def selectSources(self, predicate): """Selects sources according to predicate(src)""" busy = BusyIndicator() for src in self.model.sources: src.selected = predicate(src) self.model.emitSelection(origin=self) busy = None
def _fileSelected (self,filename,quiet=False): self.wokbtn.setEnabled(False); if not filename: return None; # check that filename matches model if not os.path.samefile(self._model_dir,os.path.dirname(filename)): self.wfile.setFilename(''); if not quiet: QMessageBox.warning(self,"Directory mismatch","""<P>The FITS file must reside in the same directory as the current sky model.</P>"""); self.wfile.setDirectory(self._model_dir); return None; # read fits file busy = BusyIndicator(); try: input_hdu = pyfits.open(filename)[0]; hdr = input_hdu.header; # get frequency, if specified for axis in range(1,hdr['NAXIS']+1): if hdr['CTYPE%d'%axis].upper() == 'FREQ': self.wfreq.setText(str(hdr['CRVAL%d'%axis]/1e+6)); break; except Exception,err: busy = None; self.wfile.setFilename(''); if not quiet: QMessageBox.warning(self,"Error reading FITS","Error reading FITS file %s: %s"%(filename,str(err))); return None;
def execute_tdl_job(self, _tdlmod, ns, func, name, job_id): """executes a predefined TDL job given by func""" self._tdlexec_menu.hide() try: # log job TDLOptions.dump_log( "running TDL job '%s' (%s)" % (name, ("job id '%s'" % job_id if job_id else "no job id"))) busy = BusyIndicator() func(meqds.mqs(), self) # no errors, so clear error list, if any self.clear_errors() self.show_message("TDL job '" + name + "' executed.", transient=True) busy = None except: (etype, exc, tb) = sys.exc_info() _dprint(0, 'exception running TDL job', func.__name__) traceback.print_exception(etype, exc, tb) # use TDL add_error() to process the error, since this automatically # adds location information. However, we want to remove ourselves # from the stack traceback first tb = traceback.extract_tb(tb) # pop everything leading up to our filename while tb[0][0] != _MODULE_FILENAME: tb.pop(0) # pop frame with our filename, this should leave only TDL-code frames tb.pop(0) ns.AddError(exc, tb, error_limit=None) msg = "TDL job '" + name + "' failed" self._error_window.set_errors(ns.GetErrors(), message=msg) self.emit(PYSIGNAL("showEditor()"), self) busy = None
def _refreshModel(self, what=SkyModel.UpdateAll, origin=None): if origin is self or not what & (SkyModel.UpdateSourceList | SkyModel.UpdateSourceContent): return # if only selection was changed, take shortcut if what & SkyModel.UpdateSelectionOnly: dprint(2, "model update -- selection only") return self._refreshSelectedItems(origin) busy = BusyIndicator() # else repopulate widget completely dprint(2, "model update -- complete") Kittens.widgets.ClickableTreeWidget.clear(self) dprint(2, "creating model items") items = [SkyModelTreeWidgetItem(src) for src in self.model.sources] self._itemdict = dict( zip([src.name for src in self.model.sources], items)) dprint(2, "adding to tree widget") self.addTopLevelItems(items) self.header().updateGeometry() # show/hide columns based on tag availability self._enableColumn(ColumnIapp, 'Iapp' in self.model.tagnames) self._enableColumn(ColumnR, 'r' in self.model.tagnames) dprint(2, "re-sorting") self.sortItems(('Iapp' in self.model.tagnames and ColumnIapp) or ColumnI, Qt.DescendingOrder) busy = None
def raiseImage (self,imagecon): # reshuffle image stack, if more than one image image if len(self._imagecons) > 1: busy = BusyIndicator(); # reshuffle image stack self._imagecons.remove(imagecon); self._imagecons.insert(0,imagecon); # notify imagecons for i,ic in enumerate(self._imagecons): label = "%d"%(i+1) if i else "<B>1</B>"; ic.setZ(self._z0-i*10,top=not i,depthlabel=label,can_raise=True); # adjust visibility for j,ic in enumerate(self._imagecons): ic.setImageVisible(not j or bool(self._qa_plot_all.isChecked())); # issue replot signal self.emit(SIGNAL("imageRaised")); self.fastReplot(); # else simply update labels else: self._imagecons[0].setZ(self._z0,top=True,depthlabel=None,can_raise=False); self._imagecons[0].setImageVisible(True); # update slice menus img = imagecon.image; axes = imagecon.renderControl().slicedAxes(); for i,(next,prev) in enumerate(self._qa_slices): next.setVisible(False); prev.setVisible(False); if i < len(axes): iaxis,name,labels = axes[i]; next.setVisible(True); prev.setVisible(True); next.setText("Show next slice along %s axis"%name); prev.setText("Show previous slice along %s axis"%name); # emit signasl self.emit(SIGNAL("imageRaised"),img);
def _select_percentile_threshold(self, percent, do_select=False): # ignore if no sort index set up, or if _select_threshold() is being called if self._sort_index is None or self._in_select_threshold: return dprint(1, "select_precentile_threshold", percent) busy = BusyIndicator() # number of objects to select nsrc = len(self._sort_index) nsel = int(math.ceil(nsrc * float(percent) / 100)) # get comparison operator opstr = str(self.wgele.currentText()) op, select = self.Operators[opstr] # select head or tail of list, depending on direction of operator if select: thr = self._sort_index[min(nsel, nsrc - 1)] slc1 = slice(0, nsel) slc2 = slice(nsel, None) else: thr = self._sort_index[-min(nsel + 1, nsrc)] slc1 = slice(nsrc - nsel, None) slc2 = slice(0, nsrc - nsel) if do_select: for val, src, cumsum in self._sort_index[slc1]: src.selected = True for val, src, cumsum in self._sort_index[slc2]: src.selected = False self.model.emitSelection(self) self.wpercent_lbl.setText("%3d%%" % percent) self.wthreshold.setText( "%g" % (thr[2] if opstr.startswith("sum") else thr[0])) return nsel
def setColorMapNumber(self, index, write_config=True): busy = BusyIndicator() self._current_cmap_index = index cmap = self._cmap_list[index] self.image.setColorMap(cmap) self.emit(SIGNAL("colorMapChanged"), cmap) if self._config and write_config: self._config.set("colour-map-number", index)
def _refreshSelectedItems(self, origin=None): busy = BusyIndicator() dprint(3, "refreshing selected items") for item in self.iterator(): if item.isSelected(): dprint(4, "resetting item", item._src.name) item.setSource(item._src) dprint(3, "refreshing selected items done") busy = None
def setIntensityMapNumber(self, index, write_config=True): busy = BusyIndicator() self._current_imap_index = index imap = self._imap_list[index][1] imap.setDataSubset(self._displaydata, self._displaydata_minmax) imap.setDataRange(*self._displayrange) self.image.setIntensityMap(imap) self.emit(SIGNAL("intensityMapChanged"), imap, index) if self._config and write_config: self._config.set("intensity-map-number", index)
def _psfFileSelected (self,filename): busy = BusyIndicator(); filename = str(filename); self.parent().showMessage("Fitting gaussian to PSF file %s"%filename); try: bmaj,bmin,pa = [ x/DEG for x in Imaging.fitPsf(filename) ]; except Exception,err: busy = None; self.qerrmsg.showMessage("Error fitting PSF file %s: %s"%(filename,str(err))); return;
def _displayAllImages (self,enabled): busy = BusyIndicator(); if enabled: for ic in self._imagecons: ic.setImageVisible(True); else: self._imagecons[0].setImageVisible(True); for ic in self._imagecons[1:]: ic.setImageVisible(False); self.replot();
def accept(self): """Tries to add brick, and closes the dialog if successful.""" filename = self.wfile.filename() # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] except Exception, err: busy = None QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return
def loadImage (self,filename=None,duplicate=True,to_top=True,model=None): """Loads image. Returns ImageControlBar object. If image is already loaded: returns old ICB if duplicate=False (raises to top if to_top=True), or else makes a new control bar. If model is set to a source name, marks the image as associated with a model source. These can be unloaded en masse by calling unloadModelImages(). """; if filename is None: if not self._load_image_dialog: dialog = self._load_image_dialog = QFileDialog(self,"Load FITS image",".","FITS images (%s);;All files (*)"%(" ".join(["*"+ext for ext in FITS_ExtensionList]))); dialog.setFileMode(QFileDialog.ExistingFile); dialog.setModal(True); QObject.connect(dialog,SIGNAL("filesSelected(const QStringList &)"),self.loadImage); self._load_image_dialog.exec_(); return None; if isinstance(filename,QStringList): filename = filename[0]; filename = str(filename); # report error if image does not exist if not os.path.exists(filename): self.showErrorMessage("""FITS image %s does not exist."""%filename); return None; # see if image is already loaded if not duplicate: for ic in self._imagecons: if ic.getFilename() and os.path.samefile(filename,ic.getFilename()): if to_top: self.raiseImage(ic); if model: self._model_imagecons.add(id(ic)); return ic; # load the FITS image busy = BusyIndicator(); dprint(2,"reading FITS image",filename); self.showMessage("""Reading FITS image %s"""%filename,3000); QApplication.flush(); try: image = SkyImage.FITSImagePlotItem(str(filename)); except KeyboardInterrupt: raise; except: busy = None; traceback.print_exc(); self.showErrorMessage("""<P>Error loading FITS image %s: %s. This may be due to a bug in Tigger; if the FITS file loads fine in another viewer, please send the FITS file, along with a copy of any error messages from the text console, to [email protected].</P>"""%(filename,str(sys.exc_info()[1]))); return None; # create control bar, add to widget stack ic = self._createImageController(image,"model source '%s'"%model if model else filename,model or image.name,model=model); self.showMessage("""Loaded FITS image %s"""%filename,3000); dprint(2,"image loaded"); return ic;
def setIntensityMapLogCycles(self, cycles, notify_image=True, write_config=True): busy = BusyIndicator() imap = self.currentIntensityMap() if isinstance(imap, Colormaps.LogIntensityMap): imap.log_cycles = cycles if notify_image: self.image.setIntensityMap() self.emit(SIGNAL("intensityMapChanged"), imap, self._current_imap_index) if self._config and write_config: self._config.set("intensity-log-cycles", cycles)
def setDisplayRange(self, dmin, dmax, notify_image=True, write_config=True): if dmax < dmin: dmin, dmax = dmax, dmin if (dmin, dmax) != self._displayrange: self._displayrange = dmin, dmax self.image.intensityMap().setDataRange(dmin, dmax) if notify_image: busy = BusyIndicator() self.image.setIntensityMap(emit=True) self.emit(SIGNAL("displayRangeChanged"), dmin, dmax) if self._config and write_config: self._config.set("range-min", dmin, save=False) self._config.set("range-max", dmax)
def _updateSlice(self, write_config=True): """Common internal method called to finalize changes to _current_slice""" busy = BusyIndicator() dprint(2, "_updateSlice", self._current_slice, time.time() % 60) indices = tuple(self._current_slice) self.image.selectSlice(*indices) dprint(2, "image slice selected", time.time() % 60) img = self.image.image() self._slicerange = self._sliceranges.get(indices) if self._slicerange is None: self._slicerange = self._sliceranges[ indices] = self.image.imageMinMax()[:2] dprint(2, "min/max updated", time.time() % 60) self.setSliceSubset(set_display_range=False) if write_config and self._config: self._config.set("slice", " ".join(map(str, indices)))
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, exc: busy = None traceback.print_exc() self._imgman.showErrorMessage( """Error writing FITS image %s: %s""" % (filename, str(sys.exc_info()[1]))) return None
def compile_content(self): # import content first, and return if failed if not self.import_content(force=True): return None _dprint(1, self._filename, "compiling forest") # clear predefined functions self._tb_tdlexec.hide() # try the compilation busy = BusyIndicator() try: (_tdlmod,ns,msg) = \ TDL.Compile.run_forest_definition( meqds.mqs(),self._filename,self._tdlmod,self._tdltext, parent=self,wait=False) # catch compilation errors except TDL.CumulativeError, value: _dprint(0, "caught cumulative error, length", len(value.args)) self._error_window.set_errors(value.args, message="TDL import failed") busy = None return None
def accept (self): """Tries to restore the image, and closes the dialog if successful."""; # get list of sources to restore sources = self.model.sources; sel_sources = filter(lambda src:src.selected,sources); if len(sel_sources) > 0 and len(sel_sources) < len(sources) and self.wselonly.isChecked(): sources = sel_sources; if not sources: self.qerrmsg.showMessage("No sources to restore."); return; busy = BusyIndicator(); # get filenames infile = self.wfile_in.filename(); outfile = self.wfile_out.filename(); self.parent().showMessage("Restoring %d model sources to image %s, writing to %s"%(len(sources),infile,outfile)); # read fits file try: input_hdu = pyfits.open(infile)[0]; except Exception,err: busy = None; self.qerrmsg.showMessage("Error reading FITS file %s: %s"%(infile,str(err))); return;
def _select_threshold(self, *dum): dprint(1, "select_threshold", dum) self._in_select_threshold = True busy = BusyIndicator() try: # get threshold, ignore if not set threshold = str(self.wthreshold.text()) if not threshold: self._reset_percentile() return # try to parse threshold, ignore if invalid try: threshold = float(threshold) except: self._reset_percentile() return # get comparison operator op, select = self.Operators[str(self.wgele.currentText())] # apply to initial segment (that matches operator) for num, entry in enumerate(self._sort_index): if not op(entry, threshold): break entry[1].selected = select else: num = len(self._sort_index) # apply to remaining segment for val, src, cumsum in self._sort_index[num:]: src.selected = not select # set percentile percent = round(float(num * 100) / len(self._sort_index)) if not select: percent = 100 - percent self.wpercent.setValue(percent) self.wpercent_lbl.setText("%3d%%" % percent) # emit signal self.model.emitSelection(self) finally: self._in_select_threshold = False 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, exc: self.emit(SIGNAL("showErrorMessage"), "Error writing %s: %s" % (filename, str(exc))) return
def accept(self): """Tries to export annotations, and closes the dialog if successful.""" try: filename = self.wfile.filename() if os.path.exists(filename) and QMessageBox.question( self, "Exporting Karma annotations", "<P>Overwrite the file %s?</P>" % filename, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) != QMessageBox.Yes: return f = file(self.wfile.filename(), "wt") f.write('COORD W\nPA STANDARD\nCOLOR GREEN\nFONT hershey12\n') # source list if self.wsel.isChecked(): sources = [src for src in self.model.sources if src.selected] else: sources = self.model.sources # calculate basis size for crosses (TODO: replace min_size with something more sensible, as this value is in degrees) brightnesses = [ abs(src.brightness()) for src in sources if src.brightness() != 0 ] min_bright = brightnesses and min(brightnesses) min_size = 0.01 # loop over sources busy = BusyIndicator() for src in sources: ra = src.pos.ra / DEG dec = src.pos.dec / DEG # figure out source size if src.brightness() and min_bright: ysize = (math.log10(abs(src.brightness())) - math.log10(min_bright) + 1) * min_size else: ysize = min_size xsize = ysize / (math.cos(src.pos.dec) or 1) # figure out source style style, label = self.model.getSourcePlotStyle(src) if style: f.write('# %s\n' % src.name) # write symbol for source f.write('COLOR %s\n' % style.symbol_color) if style.symbol == "plus": f.write('CROSS %.12f %.12f %f %f\n' % (ra, dec, xsize, ysize)) elif style.symbol == "cross": f.write('CROSS %.12f %.12f %f %f 45\n' % (ra, dec, ysize, ysize)) elif style.symbol == "circle": f.write('CIRCLE %.12f %.12f %f\n' % (ra, dec, ysize)) elif style.symbol == "dot": f.write('DOT %.12f %.12f\n' % (ra, dec)) elif style.symbol == "square": f.write('CBOX %.12f %.12f %f %f\n' % (ra, dec, xsize, ysize)) elif style.symbol == "diamond": f.write('CBOX %.12f %.12f %f %f 45\n' % (ra, dec, xsize, ysize)) # write label if label: f.write('FONT hershey%d\n' % (style.label_size * 2)) f.write('COLOR %s\n' % style.label_color) f.write('TEXT %.12f %.12f %s\n' % (ra, dec, label)) f.close() except IOError, err: busy = None self.qerrmsg.showMessage( "Error writing Karma annotations file %s: %s" % (filename, str(err))) return
class ImageManager (QWidget): """An ImageManager manages a stack of images (and associated ImageControllers)""" def __init__ (self,*args): QWidget.__init__(self,*args); # init layout self._lo = QVBoxLayout(self); self._lo.setContentsMargins(0,0,0,0); self._lo.setSpacing(0); # init internal state self._currier = PersistentCurrier(); self._z0 = 0; # z-depth of first image, the rest count down from it self._updating_imap = False; self._locked_display_range = False; self._imagecons = []; self._imagecon_loadorder = []; self._center_image = None; self._plot = None; self._border_pen = None; self._drawing_key = None; self._load_image_dialog = None; self._model_imagecons = set(); # init menu and standard actions self._menu = QMenu("&Image",self); qag = QActionGroup(self); # exclusive controls for plotting topmost or all images self._qa_plot_top = qag.addAction("Display topmost image only"); self._qa_plot_all = qag.addAction("Display all images"); self._qa_plot_top.setCheckable(True); self._qa_plot_all.setCheckable(True); self._qa_plot_top.setChecked(True); QObject.connect(self._qa_plot_all,SIGNAL("toggled(bool)"),self._displayAllImages); self._closing = False; self._qa_load_clipboard = None; self._clipboard_mode = QClipboard.Clipboard; QObject.connect(QApplication.clipboard(),SIGNAL("changed(QClipboard::Mode)"),self._checkClipboardPath); # populate the menu self._repopulateMenu(); def close (self): dprint(1,"closing Manager"); self._closing = True; for ic in self._imagecons: ic.close(); def loadImage (self,filename=None,duplicate=True,to_top=True,model=None): """Loads image. Returns ImageControlBar object. If image is already loaded: returns old ICB if duplicate=False (raises to top if to_top=True), or else makes a new control bar. If model is set to a source name, marks the image as associated with a model source. These can be unloaded en masse by calling unloadModelImages(). """; if filename is None: if not self._load_image_dialog: dialog = self._load_image_dialog = QFileDialog(self,"Load FITS image",".","FITS images (%s);;All files (*)"%(" ".join(["*"+ext for ext in FITS_ExtensionList]))); dialog.setFileMode(QFileDialog.ExistingFile); dialog.setModal(True); QObject.connect(dialog,SIGNAL("filesSelected(const QStringList &)"),self.loadImage); self._load_image_dialog.exec_(); return None; if isinstance(filename,QStringList): filename = filename[0]; filename = str(filename); # report error if image does not exist if not os.path.exists(filename): self.showErrorMessage("""FITS image %s does not exist."""%filename); return None; # see if image is already loaded if not duplicate: for ic in self._imagecons: if ic.getFilename() and os.path.samefile(filename,ic.getFilename()): if to_top: self.raiseImage(ic); if model: self._model_imagecons.add(id(ic)); return ic; # load the FITS image busy = BusyIndicator(); dprint(2,"reading FITS image",filename); self.showMessage("""Reading FITS image %s"""%filename,3000); QApplication.flush(); try: image = SkyImage.FITSImagePlotItem(str(filename)); except KeyboardInterrupt: raise; except: busy = None; traceback.print_exc(); self.showErrorMessage("""<P>Error loading FITS image %s: %s. This may be due to a bug in Tigger; if the FITS file loads fine in another viewer, please send the FITS file, along with a copy of any error messages from the text console, to [email protected].</P>"""%(filename,str(sys.exc_info()[1]))); return None; # create control bar, add to widget stack ic = self._createImageController(image,"model source '%s'"%model if model else filename,model or image.name,model=model); self.showMessage("""Loaded FITS image %s"""%filename,3000); dprint(2,"image loaded"); return ic; def showMessage (self,message,time=None): self.emit(SIGNAL("showMessage"),message,time); def showErrorMessage (self,message,time=None): self.emit(SIGNAL("showErrorMessage"),message,time); def setZ0 (self,z0): self._z0 = z0; if self._imagecons: self.raiseImage(self._imagecons[0]); def enableImageBorders (self,border_pen,label_color,label_bg_brush): self._border_pen,self._label_color,self._label_bg_brush = \ border_pen,label_color,label_bg_brush; def lockAllDisplayRanges (self,rc0): """Locks all display ranges, and sets the intensity from rc0"""; if not self._updating_imap: self._updating_imap = True; rc0.lockDisplayRange(); try: for ic in self._imagecons: rc1 = ic.renderControl(); if rc1 is not rc0: rc1.setDisplayRange(*rc0.displayRange()); rc1.lockDisplayRange(); finally: self._updating_imap = False; def unlockAllDisplayRanges (self): """Unlocks all display range."""; for ic in self._imagecons: ic.renderControl().lockDisplayRange(False); def _lockDisplayRange (self,rc0,lock): """Locks or unlocks the display range of a specific controller.""" if lock and not self._updating_imap: self._updating_imap = True; try: # if something is already locked, copy display range from it for ic in self._imagecons: rc1 = ic.renderControl(); if rc1 is not rc0 and rc1.isDisplayRangeLocked(): rc0.setDisplayRange(*rc1.displayRange()); finally: self._updating_imap = False; def _updateDisplayRange (self,rc,dmin,dmax): """This is called whenever one of the images (or rather, its associated RenderControl object) changes its display range."""; if not rc.isDisplayRangeLocked(): return; # If the display range is locked, propagate it to all images. # but don't do it if we're already propagating (otherwise we may get called in an infinte loop) if not self._updating_imap: self._updating_imap = True; try: for ic in self._imagecons: rc1 = ic.renderControl(); if rc1 is not rc and rc1.isDisplayRangeLocked(): rc1.setDisplayRange(dmin,dmax); finally: self._updating_imap = False; def getImages (self): return [ ic.image for ic in self._imagecons ]; def getTopImage (self): return (self._imagecons or None) and self._imagecons[0].image; def cycleImages (self): index = self._imagecon_loadorder.index(self._imagecons[0]); index = (index+1)%len(self._imagecon_loadorder); self.raiseImage(self._imagecon_loadorder[index]); def blinkImages (self): if len(self._imagecons)>1: self.raiseImage(self._imagecons[1]); def incrementSlice (self,extra_axis,incr): if self._imagecons: rc = self._imagecons[0].renderControl(); sliced_axes = rc.slicedAxes(); if extra_axis < len(sliced_axes): rc.incrementSlice(sliced_axes[extra_axis][0],incr); def setLMRectSubset (self,rect): if self._imagecons: self._imagecons[0].setLMRectSubset(rect); def getLMRectStats (self,rect): if self._imagecons: return self._imagecons[0].renderControl().getLMRectStats(rect); def unloadModelImages (self): """Unloads images associated with model (i.e. loaded with the model=True flag)"""; for ic in [ ic for ic in self._imagecons if id(ic) in self._model_imagecons ]: self.unloadImage(ic); def unloadImage (self,imagecon): """Unloads the given imagecon object."""; if imagecon not in self._imagecons: return; # recenter if needed self._imagecons.remove(imagecon); self._imagecon_loadorder.remove(imagecon); self._model_imagecons.discard(id(imagecon)); # reparent widget and release it imagecon.setParent(None); imagecon.close(); # recenter image, if unloaded the center image if self._center_image is imagecon.image: self.centerImage(self._imagecons[0] if self._imagecons else None,emit=False); # emit signal self._repopulateMenu(); self.emit(SIGNAL("imagesChanged")); if self._imagecons: self.raiseImage(self._imagecons[0]); def getCenterImage (self): return self._center_image; def centerImage (self,imagecon,emit=True): self._center_image = imagecon and imagecon.image; for ic in self._imagecons: ic.setPlotProjection(self._center_image.projection); if emit: self.emit(SIGNAL("imagesChanged")); def raiseImage (self,imagecon): # reshuffle image stack, if more than one image image if len(self._imagecons) > 1: busy = BusyIndicator(); # reshuffle image stack self._imagecons.remove(imagecon); self._imagecons.insert(0,imagecon); # notify imagecons for i,ic in enumerate(self._imagecons): label = "%d"%(i+1) if i else "<B>1</B>"; ic.setZ(self._z0-i*10,top=not i,depthlabel=label,can_raise=True); # adjust visibility for j,ic in enumerate(self._imagecons): ic.setImageVisible(not j or bool(self._qa_plot_all.isChecked())); # issue replot signal self.emit(SIGNAL("imageRaised")); self.fastReplot(); # else simply update labels else: self._imagecons[0].setZ(self._z0,top=True,depthlabel=None,can_raise=False); self._imagecons[0].setImageVisible(True); # update slice menus img = imagecon.image; axes = imagecon.renderControl().slicedAxes(); for i,(next,prev) in enumerate(self._qa_slices): next.setVisible(False); prev.setVisible(False); if i < len(axes): iaxis,name,labels = axes[i]; next.setVisible(True); prev.setVisible(True); next.setText("Show next slice along %s axis"%name); prev.setText("Show previous slice along %s axis"%name); # emit signasl self.emit(SIGNAL("imageRaised"),img); def resetDrawKey (self): """Makes and sets the current plot's drawing key""" if self._plot: key = []; for ic in self._imagecons: key.append(id(ic)); key += ic.currentSlice(); self._plot.setDrawingKey(tuple(key)); def fastReplot (self,*dum): """Fast replot -- called when flipping images or slices. Uses the plot cache, if possible."""; if self._plot: self.resetDrawKey(); dprint(2,"calling replot",time.time()%60); self._plot.replot(); dprint(2,"replot done",time.time()%60); def replot (self,*dum): """Proper replot -- called when an image needs to be properly redrawn. Cleares the plot's drawing cache."""; if self._plot: self._plot.clearDrawCache(); self.resetDrawKey(); self._plot.replot(); def attachImagesToPlot (self,plot): self._plot = plot; self.resetDrawKey(); for ic in self._imagecons: ic.attachToPlot(plot); def getMenu (self): return self._menu; def _displayAllImages (self,enabled): busy = BusyIndicator(); if enabled: for ic in self._imagecons: ic.setImageVisible(True); else: self._imagecons[0].setImageVisible(True); for ic in self._imagecons[1:]: ic.setImageVisible(False); self.replot(); def _checkClipboardPath (self,mode=QClipboard.Clipboard): if self._qa_load_clipboard: self._clipboard_mode = mode; try: path = str(QApplication.clipboard().text(mode)); except: path = None; self._qa_load_clipboard.setEnabled(bool(path and os.path.isfile(path))); def _loadClipboardPath (self): try: path = QApplication.clipboard().text(self._clipboard_mode); except: return; self.loadImage(path); def _repopulateMenu (self): self._menu.clear(); self._menu.addAction("&Load image...",self.loadImage,Qt.CTRL+Qt.Key_L); self._menu.addAction("&Compute image...",self.computeImage,Qt.CTRL+Qt.Key_M); self._qa_load_clipboard = self._menu.addAction("Load from clipboard &path",self._loadClipboardPath,Qt.CTRL+Qt.Key_P); self._checkClipboardPath(); if self._imagecons: self._menu.addSeparator(); # add controls to cycle images and planes for i,imgcon in enumerate(self._imagecons[::-1]): self._menu.addMenu(imgcon.getMenu()); self._menu.addSeparator(); if len(self._imagecons) > 1: self._menu.addAction("Cycle images",self.cycleImages,Qt.Key_F5); self._menu.addAction("Blink images",self.blinkImages,Qt.Key_F6); self._qa_slices = (( self._menu.addAction("Next slice along axis 1",self._currier.curry(self.incrementSlice,0,1),Qt.Key_F7), self._menu.addAction("Previous slice along axis 1",self._currier.curry(self.incrementSlice,0,-1),Qt.SHIFT+Qt.Key_F7)), ( self._menu.addAction("Next slice along axis 2",self._currier.curry(self.incrementSlice,1,1),Qt.Key_F8), self._menu.addAction("Previous slice along axis 2",self._currier.curry(self.incrementSlice,1,-1),Qt.SHIFT+Qt.Key_F8))); self._menu.addSeparator(); self._menu.addAction(self._qa_plot_top); self._menu.addAction(self._qa_plot_all); def computeImage (self,expression=None): """Computes image from expression (if expression is None, pops up dialog)"""; if expression is None: (expression,ok) = QInputDialog.getText(self,"Compute image", """Enter an image expression to compute. Any valid numpy expression is supported, and all functions from the numpy module are available (including sub-modules such as fft). Use 'a', 'b', 'c' to refer to images. Examples: "(a+b)/2", "cos(a)+sin(b)", "a-a.mean()", "fft.fft2(a)", etc."""); # (expression,ok) = QInputDialog.getText(self,"Compute image","""<P>Enter an expression to compute. # Use 'a', 'b', etc. to refer to loaded images. Any valid numpy expression is supported, and all the # functions from the numpy module are available. Examples of valid expressions include "(a+b)/2", # "cos(a)+sin(b)", "a-a.mean()", etc. # </P> # """); expression = str(expression); if not ok or not expression: return; # try to parse expression arglist = [ (chr(ord('a')+ic.getNumber()),ic.image) for ic in self._imagecons ]; try: exprfunc = eval("lambda "+(",".join([ x[0] for x in arglist ]))+":"+expression, numpy.__dict__,{}); except Exception,exc: self.showErrorMessage("""Error parsing expression "%s": %s."""%(expression,str(exc))); return None; # try to evaluate expression self.showMessage("Computing expression \"%s\""%expression,10000); busy = BusyIndicator(); QApplication.flush(); # trim trivial trailing dimensions. This avoids the problem of when an NxMx1 and an NxMx1x1 arrays are added, # the result is promoted to NxMxMx1 following the numpy rules. def trimshape (shape): out = shape; while out and out[-1] == 1: out = out[:-1]; return out; def trimarray (array): return array.reshape(trimshape(array.shape)); try: result = exprfunc(*[trimarray(x[1].data()) for x in arglist]); except Exception,exc: busy = None; traceback.print_exc(); self.showErrorMessage("""Error evaluating "%s": %s."""%(expression,str(exc))); return None;
def _enableColumn(self, column, enable=True): busy = BusyIndicator() self._column_enabled[column] = enable self._showColumn(column, enable and self._column_shown[column])
def _showColumnCategory(self, columns, show): busy = BusyIndicator() for col in columns: self._column_shown[col] = show self._showColumn(col, self._column_enabled[col] and show)
# look for an image in the arglist with the same projection, and with a valid dirname # (for the where-to-save hint) template = arglist[0][1]; # if all images in arglist have the same projection, then it doesn't matter what we use # else ask if len([x for x in arglist[1:] if x[1].projection == template.projection]) != len(arglist)-1: options = [ x[0] for x in arglist ]; (which,ok) = QInputDialog.getItem(self,"Compute image","Coordinate system to use for the result of \"%s\":"%expression,options,0,False); if not ok: return None; try: template = arglist[options.index(which)][1]; except: pass; # create a FITS image busy = BusyIndicator(); dprint(2,"creating FITS image",expression); self.showMessage("""Creating image for %s"""%expression,3000); QApplication.flush(); try: hdu = pyfits.PrimaryHDU(result.transpose(),template.fits_header); skyimage = SkyImage.FITSImagePlotItem(name=expression,filename=None,hdu=hdu); except: busy = None; traceback.print_exc(); self.showErrorMessage("""Error creating FITS image %s: %s"""%(expression,str(sys.exc_info()[1]))); return None; # get directory name for save-to hint dirname = getattr(template,'filename',None); if not dirname: dirnames = [ getattr(img,'filename') for x,img in arglist if hasattr(img,'filename') ];
def import_content(self, force=False, show_options=False): """imports TDL module but does not run _define_forest(). Depending on autosync/modified state, asks to save or revert. If module is already imported, does nothing, unless force=True, in which case it imports unconditionally. If do_compile=True, proceeds to show compile-time options on success, or to compile directly if there are no options Return value: True on successful import None if cancelled by user. Import errors are posted to the error dialog. """ # save list of publishers (since forest will be cleared on import) if self._pub_nodes is None: if TDL.Compile.last_imported_file() == self._filename: self._pub_nodes = [ node.name for node in meqds.nodelist.iternodes() if node.is_publishing() ] _dprint(2, "last imported file matches, saving", len(self._pub_nodes), "publishers") else: self._pub_nodes = [] _dprint(2, "last imported file doesn't match:", TDL.Compile.last_imported_file(), self._filename) _dprint(1, self._filename, "importing") self.clear_message() self.clear_errors() self._options_menu.hide() self._tb_tdlexec.hide() self._tdlexec_menu.hide() # change the current directory to where the file is # os.chdir(os.path.dirname(self._filename)); # The Python imp module expects text to reside in a disk file, which is # a pain in the ass for us if we're dealing with modified text or text # entered on-the-fly. So, either save or sync before proceeding global _external_sync if self._document.isModified() or not self._filename: if not self._save_file(): return None else: if not self.sync_external_file(ask=False): return None # if we already have an imported module and disk file hasn't changed, skip # the importing step busy = BusyIndicator() if force or self._tdlmod is None or self._tdlmod_filetime == self._file_disktime: # reset data members _dprint(2, self._filename, "emitting signal for 0 compile-time options") self.emit(PYSIGNAL("hasCompileOptions()"), self, 0) self._options_menu.hide() self._options_menu.clear() self._tdlmod = None # get text from editor tdltext = str(self._document.toPlainText()) try: tdlmod, tdltext = TDL.Compile.import_tdl_module( self._filename, tdltext) # catch import errors except TDL.CumulativeError as value: _dprint(0, "caught cumulative error, length", len(value.args)) self._error_window.set_errors(value.args, message="TDL import failed") busy = None return None except Exception as value: _dprint(0, "caught other error, traceback follows") traceback.print_exc() self._error_window.set_errors([value], message="TDL import failed") busy = None return None # remember module and nodescope self._tdlmod = tdlmod self._tdltext = tdltext self._tdlmod_filetime = self._file_disktime # build options menu from compile-time options opt_tw = self._options_menu.treeWidget() opts = TDLOptions.get_compile_options() if opts: # add options try: TDLOptions.populate_option_treewidget(opt_tw, opts) except Exception as value: _dprint(0, "error setting up TDL options GUI") traceback.print_exc() self._error_window.set_errors( [value], message="Error setting up TDL options GUI") busy = None return None # self._tb_opts.show(); _dprint(2, self._filename, "emitting signal for", len(opts), "compile-time options") self.emit(PYSIGNAL("hasCompileOptions()"), self, len(opts)) # success, show options or compile if show_options and self.has_compile_options(): self._options_menu.adjustSizes() self._options_menu.show() busy = None return True
def compile_content(self): # import content first, and return if failed if not self.import_content(force=True): return None _dprint(1, self._filename, "compiling forest") # clear predefined functions self._tb_tdlexec.hide() # try the compilation busy = BusyIndicator() try: (_tdlmod,ns,msg) = \ TDL.Compile.run_forest_definition( meqds.mqs(),self._filename,self._tdlmod,self._tdltext, parent=self,wait=False) # catch compilation errors except TDL.CumulativeError as value: _dprint(0, "caught cumulative error, length", len(value.args)) self._error_window.set_errors(value.args, message="TDL import failed") busy = None return None except Exception as value: _dprint(0, "caught other error, traceback follows") traceback.print_exc() self._error_window.set_errors([value]) busy = None return None # refresh the nodelist meqds.request_nodelist(sync=True) # restore publishing nodes for name in (self._pub_nodes or []): if name in ns.AllNodes(): _dprint(2, "reenabling publishing of", name) meqds.enable_node_publish_by_name(name, sync=True, get_state=True) else: _dprint(2, "not reenabling publishing of", name, ", as it no longer exists?") # clear publisher list so it's re-saved next time in import_content() self._pub_nodes = None ### NB: presume this all was successful for now # insert node scope into TDL module setattr(_tdlmod, '_tdl_nodescope', ns) # does the script define an explicit job list? joblist = getattr(_tdlmod, '_tdl_job_list', []) if not joblist: joblist = [] # try to build it from implicit function names for (name, func) in _tdlmod.__dict__.items(): if name.startswith("_tdl_job_") and callable( func) and not TDLOptions.is_jobfunc_defined(func): joblist.append(func) # does the script define a testing function? testfunc = getattr(_tdlmod, '_test_forest', None) if not callable(testfunc): testfunc = getattr(_tdlmod, 'test_forest', None) if callable(testfunc): res = QMessageBox.warning( self, "Deprecated method", """Your script contains a test_forest() method. This is deprecated and will be disabled in the future. Please rename it to _test_forest(). """, QMessageBox.Ok) if callable(testfunc): joblist.append(testfunc) from past.builtins import cmp from functools import cmp_to_key joblist.sort(key=cmp_to_key(lambda a, b: cmp(str(a), str(b)))) # create list of job actions opts = TDLOptions.get_runtime_options() self._tdlexec_menu.clear() if joblist or opts: if opts: self._job_executor = curry(self.execute_tdl_job, _tdlmod, ns) ## new style: try: TDLOptions.populate_option_treewidget( self._tdlexec_menu.treeWidget(), opts, executor=self._job_executor) except Exception as value: _dprint(0, "error setting up TDL options GUI") traceback.print_exc() self._error_window.set_errors( [value], message="Error setting up TDL options GUI") busy = None return None if joblist: for func in joblist: name = re.sub("^_tdl_job_", "", func.__name__) name = name.replace('_', ' ') self._tdlexec_menu.addAction(name, curry(self.execute_tdl_job, _tdlmod, ns, func, name, func.__name__), icon=pixmaps.gear) self.emit(PYSIGNAL("hasRuntimeOptions()"), self, True) self._tdlexec_menu.adjustSizes() self._tb_tdlexec.show() else: self.emit(PYSIGNAL("hasRuntimeOptions()"), self, False) self._tb_tdlexec.hide() if joblist: msg += " %d predefined function(s) available, please use the Exec menu to run them." % ( len(joblist), ) self.show_message(msg, transient=True) busy = None return True
def updateColorMapParameters(self): """Call this when the colormap parameters have changed""" busy = BusyIndicator() self.image.updateCurrentColorMap() if self._config: self._cmap_list[self._current_cmap_index].saveConfig(self._config)