def _showMeanStd(self, busy=True): if busy: busy = BusyIndicator() dmin, dmax = self._subset_range subset, mask = self.image.optimalRavel(self._subset) dprint(5, "computing mean") mean = measurements.mean(subset, labels=mask, index=None if mask is None else False) dprint(5, "computing std") std = measurements.standard_deviation(subset, labels=mask, index=None if mask is None else False) dprint(5, "done") text = " ".join([("%s: " + DataValueFormat) % (name, value) for name, value in (("min", dmin), ("max", dmax), ("mean", mean), ("\n std", std))] + ["np: %d" % self._subset.size]) self._wlab_stats.setText(text) self._wmore_stats.hide() # update markers ypos = 0.3 self._line_mean.line.setData([mean, mean], [0, 1]) self._line_mean.marker.setValue(mean, ypos) self._line_mean.setText(("\u03BC=" + DataValueFormat) % mean) self._line_mean.show() self._line_std.line.setData([mean - std, mean + std], [ypos, ypos]) self._line_std.marker.setValue(mean, ypos) self._line_std.setText(("\u03C3=" + DataValueFormat) % std) self._line_std.show() self._histplot.replot() if not isinstance(busy, bool): busy.reset_cursor()
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])) busy.reset_cursor() return nsel
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") TigGUI.kitties.widgets.ClickableTreeWidget.clear(self) dprint(2, "creating model items") items = [SkyModelTreeWidgetItem(src) for src in self.model.sources] self._itemdict = dict( list(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.reset_cursor()
def selectSources(self, predicate, curry=False): """Selects sources according to predicate(src)""" busy = BusyIndicator() for src in self.model.sources: src.selected = predicate(src) self.model.emitSelection(origin=self) busy.reset_cursor()
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) busy.reset_cursor()
def _unselectAll(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = False self.model.emitSelection(self) busy.reset_cursor()
def _selectInvert(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = not src.selected self.model.emitSelection(self) busy.reset_cursor()
def setColorMapNumber(self, index, write_config=True): busy = BusyIndicator() self._current_cmap_index = index cmap = self._cmap_list[index] self.image.setColorMap(cmap) self.colorMapChanged.emit(cmap) if self._config and write_config: self._config.set("colour-map-number", index) busy.reset_cursor()
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.reset_cursor()
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.intensityMapChanged.emit(imap, index) if self._config and write_config: self._config.set("intensity-map-number", index) busy.reset_cursor()
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() busy.reset_cursor()
def raiseImage(self, imagecon, foo=None): busy = None # 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 fixed with assumption that this signal is now correct according to the old version # self.imageRaised.emit(self._imagecons[0]) # This was the old signal self.imagePlotRaised.emit() 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 signals self.imageRaised.emit(img) # if dockable control dialog is docked and tabbed then raise to front if imagecon._dockable_colour_ctrl is not None: if imagecon._dockable_colour_ctrl.isVisible(): if not imagecon._dockable_colour_ctrl.isFloating(): list_of_tabbed_widgets = self.mainwin.tabifiedDockWidgets( imagecon._dockable_colour_ctrl) if list_of_tabbed_widgets: imagecon._dockable_colour_ctrl.raise_() if busy is not None: busy.reset_cursor()
def dropEvent(self, event): busy = None filenames = self._getFilenamesFromDropEvent(event) dprint(1, "dropping", filenames) if filenames: event.acceptProposedAction() busy = BusyIndicator() for name in filenames: self.imgman.loadImage(name) if busy is not None: busy.reset_cursor()
def show(self): dprint(4, "show entrypoint") if self._geometry: dprint(4, "setting geometry") self.setGeometry(self._geometry) if self._hist is None: busy = BusyIndicator() dprint(4, "updating histogram") self._updateHistogram() dprint(4, "updating stats") self._updateStats(self._subset, self._subset_range) busy.reset_cursor() dprint(4, "calling QDialog.show") QDialog.show(self)
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.intensityMapChanged.emit(imap, self._current_imap_index) if self._config and write_config: self._config.set("intensity-log-cycles", cycles) busy.reset_cursor()
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 _selectAll(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = True self.model.emitSelection(self)
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))) busy.reset_cursor()
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) busy.reset_cursor() self.displayRangeChanged.emit(dmin, dmax) if self._config and write_config: self._config.set("range-min", dmin, save=False) self._config.set("range-max", dmax)
def _changeDisplayRangeToPercent(self, percent): busy = BusyIndicator() if self._hist is None: self._updateHistogram() self._updateStats(self._subset, self._subset_range) # delta: we need the [delta,100-delta] interval of the total distribution delta = self._subset.size * ((100. - percent) / 200.) # get F(x): cumulative sum cumsum = numpy.zeros(len(self._hist_hires) + 1, dtype=int) cumsum[1:] = numpy.cumsum(self._hist_hires) bins = numpy.zeros(len(self._hist_hires) + 1, dtype=float) bins[0] = self._subset_range[0] bins[1:] = self._hist_bins_hires + self._hist_binsize_hires / 2 # use interpolation to find value interval corresponding to [delta,100-delta] of the distribution dprint(2, self._subset.size, delta, self._subset.size - delta) dprint(2, cumsum, self._hist_bins_hires) # if first bin is already > delta, then set colour range to first bin x0, x1 = numpy.interp([delta, self._subset.size - delta], cumsum, bins) # and change the display range (this will also cause a histplot.replot() via _updateDisplayRange above) self._rc.setDisplayRange(x0, x1) busy.reset_cursor()
def _saveImage(self): filename = QFileDialog.getSaveFileName( self, "Save FITS file", self._save_dir, "FITS files(*.fits *.FITS *fts *FTS)", options=QFileDialog.DontUseNativeDialog) filename = str(filename[0]) if not filename: return busy = BusyIndicator() self._imgman.signalShowMessage.emit( """Writing FITS image %s""" % filename, 3000) QApplication.flush() try: self.image.save(filename) except Exception as exc: busy.reset_cursor() traceback.print_exc() self._imgman.signalShowErrorMessage.emit( """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.reset_cursor()
def saveFile(self, filename=None, confirm=False, overwrite=True, non_native=False): """Saves file using the specified 'filename'. If filename is None, uses current filename, if that is not set, goes to saveFileAs() to open dialog and get a filename. If overwrite=False, will ask for confirmation before overwriting an existing file. If non_native=False, will ask for confirmation before exporting in non-native format. If confirm=True, will ask for confirmation regardless. Returns True if saving succeeded, False on error (or if cancelled by user). """ if isinstance(filename, QStringList): filename = filename[0] filename = (filename and str(filename)) or self.filename if filename is None: return self.saveFileAs() else: warning = '' # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( filename, None) if export_func is None: self.signalShowErrorMessage.emit( """Error saving model file %s: unsupported output format""" % filename) return if os.path.exists(filename) and not overwrite: warning += "<P>The file already exists and will be overwritten.</P>" if filetype != 'Tigger' and not non_native: warning += """<P>Please note that you are exporting the model using the external format '%s'. Source types, tags and other model features not supported by this format will be omitted during the export.</P>""" % filetype # get confirmation if confirm or warning: dialog = QMessageBox.warning if warning else QMessageBox.question if dialog(self, "Saving sky model", "<P>Save model to %s?</P>%s" % (filename, warning), QMessageBox.Save | QMessageBox.Cancel, QMessageBox.Save) != QMessageBox.Save: return False busy = BusyIndicator() try: export_func(self.model, filename) self.model.setFilename(filename) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error saving model file %s: %s""" % (filename, str(sys.exc_info()[1]))) return False else: self.signalShowMessage.emit( """Saved model to file %s""" % filename, 3000) self._display_filename = os.path.basename(filename) self._indicateModelUpdated(updated=False) self.filename = filename return True finally: busy.reset_cursor()
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 = [src for src in sources if src.selected] 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 as err: busy = None self.qerrmsg.showMessage("Error reading FITS file %s: %s" % (infile, str(err))) return # get beam sizes try: bmaj = float(str(self.wbmaj.text())) bmin = float(str(self.wbmin.text())) pa = float(str(self.wbpa.text()) or "0") except Exception as err: busy = None self.qerrmsg.showMessage("Invalid beam size specified") return bmaj = bmaj / (Imaging.FWHM * 3600) * DEG bmin = bmin / (Imaging.FWHM * 3600) * DEG pa = pa * DEG # restore try: Imaging.restoreSources(input_hdu, sources, bmaj, bmin, pa) except Exception as err: busy = None self.qerrmsg.showMessage("Error restoring model into image: %s" % str(err)) return # save fits file try: input_hdu.writeto(outfile, clobber=True) except Exception as err: busy = None self.qerrmsg.showMessage("Error writing FITS file %s: %s" % (outfile, str(err))) return self.parent().loadImage(outfile) busy = None return QDialog.accept(self)
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.reset_cursor()
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 as err: busy = None self.qerrmsg.showMessage("Error fitting PSF file %s: %s" % (filename, str(err))) return bmaj *= 3600 * Imaging.FWHM bmin *= 3600 * Imaging.FWHM self.wbmaj.setText(str(bmaj)) self.wbmin.setText(str(bmin)) self.wbpa.setText(str(pa))
def openFile(self, _filename=None, _format=None, _merge=False, _show=True): # check that we can close existing model if not _merge and not self._canCloseExistingModel(): return False if isinstance(_filename, QStringList): _filename = _filename[0] _filename = str(_filename) # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( _filename, _format) if import_func is None: self.signalShowErrorMessage.emit( """Error loading model file %s: unknown file format""" % _filename) return # try to load the specified file busy = BusyIndicator() self.signalShowMessage.emit( """Reading %s file %s""" % (filetype, _filename), 3000) QApplication.flush() try: model = import_func(_filename) model.setFilename(_filename) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error loading '%s' file %s: %s""" % (filetype, _filename, str(sys.exc_info()[1]))) return else: # set the layout if _show: self.setLayout(self.LayoutImageModel) # add to content if _merge and self.model: self.model.addSources(model.sources) self.signalShowMessage.emit( """Merged in %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename), 3000) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll) else: print("""Loaded %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename)) self.signalShowMessage.emit( """Loaded %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename), 3000) self._display_filename = os.path.basename(_filename) self.setModel(model) self._indicateModelUpdated(updated=False) # only set self.filename if an export function is available for this format. Otherwise set it to None, so that trying to save # the file results in a save-as operation (so that we don't save to a file in an unsupported format). self.filename = _filename if export_func else None finally: busy.reset_cursor()
def saveSelectionAs(self, filename=None, force=False): if not self.model: return if filename is None: if not self._save_sel_as_dialog: filters = ";;".join([ "%s (%s)" % (name, " ".join(patterns)) for name, patterns, func in self._save_file_types ]) dialog = self._save_sel_as_dialog = QFileDialog( self, "Save sky model", ".", filters) dialog.setDefaultSuffix(ModelHTML.DefaultExtension) dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setOption(QFileDialog.DontConfirmOverwrite, False) dialog.setModal(True) dialog.filesSelected['QStringList'].connect( self.saveSelectionAs) return self._save_sel_as_dialog.exec_() == QDialog.Accepted # save selection if isinstance(filename, QStringList): filename = filename[0] filename = str(filename) selmodel = self.model.copy() sources = [src for src in self.model.sources if src.selected] if not sources: self.signalShowErrorMessage.emit( """You have not selected any sources to save.""") return # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( filename, None) if export_func is None: self.signalShowErrorMessage.emit( """Error saving model file %s: unsupported output format""" % filename) return busy = BusyIndicator() try: export_func(self.model, filename, sources=sources) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error saving selection to model file %s: %s""" % (filename, str(sys.exc_info()[1]))) return False else: self.signalShowMessage.emit( """Wrote %d selected source%s to file %s""" % (len(selmodel.sources), "" if len(selmodel.sources) == 1 else "s", filename), 3000) finally: busy.reset_cursor() pass
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 as 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 self.wokbtn.setEnabled(True) # if filename is not in model already, enable the "add to model" control for src in self.model.sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) \ and os.path.exists(src.shape.filename) and os.path.exists(filename) \ and os.path.samefile(src.shape.filename, filename): self.wadd.setChecked(True) self.wadd.setEnabled(False) self.wadd.setText("image already in sky model") break else: self.wadd.setText("add image to sky model") return filename
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 accept(self): """Tries to make a brick, and closes the dialog if successful.""" sources = [ src for src in self.model.sources if src.selected and src.typecode == 'pnt' ] filename = self.wfile.filename() if not self._fileSelected(filename): return # get PB expression pbfunc = None if self.wpb_apply.isChecked(): pbexp = str(self.wpb_exp.text()) try: pbfunc = eval("lambda r,fq:" + pbexp) except Exception as err: QMessageBox.warning( self, "Error parsing PB experssion", "Error parsing primary beam expression %s: %s" % (pbexp, str(err))) return # get frequency freq = str(self.wfreq.text()) freq = float(freq) * 1e+6 if freq else None # get pad factor pad = str(self.wpad.text()) pad = max(float(pad), 1) if pad else 1 # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] except Exception as err: busy = None QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return # reset data if asked to if self.woverwrite.isChecked(): input_hdu.data[...] = 0 # insert sources Imaging.restoreSources(input_hdu, sources, 0, primary_beam=pbfunc, freq=freq) # save fits file try: # pyfits seems to produce an exception: # TypeError: formatwarning() takes exactly 4 arguments (5 given) # when attempting to overwrite a file. As a workaround, remove the file first. if os.path.exists(filename): os.remove(filename) input_hdu.writeto(filename) except Exception as err: traceback.print_exc() busy = None QMessageBox.warning( self, "Error writing FITS", "Error writing FITS file %s: %s" % (filename, str(err))) return changed = False sources = self.model.sources # remove sources from model if asked to if self.wdel.isChecked(): sources = [ src for src in sources if not (src.selected and src.typecode == 'pnt') ] changed = True # add image to model if asked to if self.wadd.isChecked(): hdr = input_hdu.header # get image parameters max_flux = float(input_hdu.data.max()) wcs = WCS(hdr, mode='pyfits') # Get reference pixel coordinates # wcs.getCentreWCSCoords() doesn't work, as that gives us the middle of the image # So scan the header to get the CRPIX values ra0 = dec0 = 1 for iaxis in range(hdr['NAXIS']): axs = str(iaxis + 1) name = hdr.get('CTYPE' + axs, axs).upper() if name.startswith("RA"): ra0 = hdr.get('CRPIX' + axs, 1) - 1 elif name.startswith("DEC"): dec0 = hdr.get('CRPIX' + axs, 1) - 1 # convert pixel to degrees ra0, dec0 = wcs.pix2wcs(ra0, dec0) ra0 *= DEG dec0 *= DEG sx, sy = wcs.getHalfSizeDeg() sx *= DEG sy *= DEG nx, ny = input_hdu.data.shape[-1:-3:-1] # check if this image is already contained in the model for src in sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) and os.path.samefile( src.shape.filename, filename): # update source parameters src.pos.ra, src.pos.dec = ra0, dec0 src.flux.I = max_flux src.shape.ex, src.shape.ey = sx, sy src.shape.nx, src.shape.ny = nx, ny src.shape.pad = pad break # not contained, make new source object else: pos = ModelClasses.Position(ra0, dec0) flux = ModelClasses.Flux(max_flux) shape = ModelClasses.FITSImage(sx, sy, 0, os.path.basename(filename), nx, ny, pad=pad) img_src = SkyModel.Source(os.path.splitext( os.path.basename(filename))[0], pos, flux, shape=shape) sources.append(img_src) changed = True if changed: self.model.setSources(sources) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self) self.parent().showMessage("Wrote %d sources to FITS file %s" % (len(sources), filename)) busy = None return QDialog.accept(self)