Example #1
0
class ExportKarmaDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Export Karma annotations")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # file selector
        self.wfile = FileSelector(self,
                                  label="Filename:",
                                  dialog_label="Karma annotations filename",
                                  default_suffix="ann",
                                  file_types="Karma annotations (*.ann)")
        lo.addWidget(self.wfile)
        # selected sources checkbox
        self.wsel = QCheckBox("selected sources only", self)
        lo.addWidget(self.wsel)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        QObject.connect(self.wfile, SIGNAL("valid"), self.wokbtn.setEnabled)
        # internal state
        self.qerrmsg = QErrorMessage(self)
        self._model_filename = None

    def setModel(self, model):
        self.model = model
        # set the default annotations filename, whenever a new model filename is set
        filename = self.model.filename()
        if filename and filename != self._model_filename:
            self._model_filename = filename
            self.wfile.setFilename(os.path.splitext(filename)[0] + ".ann")

    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 as err:
            busy = None
            self.qerrmsg.showMessage(
                "Error writing Karma annotations file %s: %s" %
                (filename, str(err)))
            return
        busy = None
        self.parent().showMessage(
            "Wrote Karma annotations for %d sources to file %s" %
            (len(sources), filename))
        return QDialog.accept(self)
Example #2
0
class MakeBrickDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Convert sources to FITS brick")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # file selector
        self.wfile = FileSelector(self,
                                  label="FITS filename:",
                                  dialog_label="Output FITS file",
                                  default_suffix="fits",
                                  file_types="FITS files (*.fits *.FITS)",
                                  file_mode=QFileDialog.ExistingFile)
        lo.addWidget(self.wfile)
        # reference frequency
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        label = QLabel("Frequency, MHz:", self)
        lo1.addWidget(label)
        tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated
    for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>"""
        self.wfreq = QLineEdit(self)
        self.wfreq.setValidator(QDoubleValidator(self))
        label.setToolTip(tip)
        self.wfreq.setToolTip(tip)
        lo1.addWidget(self.wfreq)
        # beam gain
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpb_apply = QCheckBox("Apply primary beam expression:", self)
        self.wpb_apply.setChecked(True)
        lo1.addWidget(self.wpb_apply)
        tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting
    them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding
    to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>"""
        self.wpb_exp = QLineEdit(self)
        self.wpb_apply.setToolTip(tip)
        self.wpb_exp.setToolTip(tip)
        lo1.addWidget(self.wpb_exp)
        # overwrite or add mode
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.woverwrite = QRadioButton("overwrite image", self)
        self.woverwrite.setChecked(True)
        lo1.addWidget(self.woverwrite)
        self.waddinto = QRadioButton("add into image", self)
        lo1.addWidget(self.waddinto)
        # add to model
        self.wadd = QCheckBox(
            "Add resulting brick to sky model as a FITS image component", self)
        lo.addWidget(self.wadd)
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpad = QLineEdit(self)
        self.wpad.setValidator(QDoubleValidator(self))
        self.wpad.setText("1.1")
        lab = QLabel("...with padding factor:", self)
        lab.setToolTip(
            """<P>The padding factor determines the amount of null padding inserted around the image during
      the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show
      up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size.
      This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is
      perfectly fine.</P>""")
        self.wpad.setToolTip(lab.toolTip())
        QObject.connect(self.wadd, SIGNAL("toggled(bool)"),
                        self.wpad.setEnabled)
        QObject.connect(self.wadd, SIGNAL("toggled(bool)"), lab.setEnabled)
        self.wpad.setEnabled(False)
        lab.setEnabled(False)
        lo1.addStretch(1)
        lo1.addWidget(lab, 0)
        lo1.addWidget(self.wpad, 1)
        self.wdel = QCheckBox(
            "Remove from the sky model sources that go into the brick", self)
        lo.addWidget(self.wdel)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        QObject.connect(self.wfile, SIGNAL("filenameSelected"),
                        self._fileSelected)
        # internal state
        self.qerrmsg = QErrorMessage(self)

    def setModel(self, model):
        self.model = model
        pb = self.model.primaryBeam()
        if pb:
            self.wpb_exp.setText(pb)
        else:
            self.wpb_apply.setChecked(False)
            self.wpb_exp.setText("")
        if model.filename():
            self._model_dir = os.path.dirname(os.path.abspath(
                model.filename()))
        else:
            self._model_dir = os.path.abspath('.')
        self.wfile.setDirectory(self._model_dir)
        self._fileSelected(self.wfile.filename(), quiet=True)

    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 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)
Example #3
0
class RestoreImageDialog (QDialog):
  def __init__ (self,parent,modal=True,flags=Qt.WindowFlags()):
    QDialog.__init__(self,parent,flags);
    self.setModal(modal);
    self.setWindowTitle("Restore model into image");
    lo = QVBoxLayout(self);
    lo.setMargin(10);
    lo.setSpacing(5);
    # file selector
    self.wfile_in = FileSelector(self,label="Input FITS file:",dialog_label="Input FITS file",default_suffix="fits",file_types="FITS files (*.fits *.FITS)",file_mode=QFileDialog.ExistingFile);
    lo.addWidget(self.wfile_in);
    self.wfile_out = FileSelector(self,label="Output FITS file:",dialog_label="Output FITS file",default_suffix="fits",file_types="FITS files (*.fits *.FITS)",file_mode=QFileDialog.AnyFile);
    lo.addWidget(self.wfile_out);
    # beam size
    lo1 = QHBoxLayout();
    lo.addLayout(lo1);
    lo1.setContentsMargins(0,0,0,0);
    lo1.addWidget(QLabel("Restoring beam FWHM, major axis:",self));
    self.wbmaj = QLineEdit(self);
    lo1.addWidget(self.wbmaj);
    lo1.addWidget(QLabel("\"     minor axis:",self));
    self.wbmin = QLineEdit(self);
    lo1.addWidget(self.wbmin);
    lo1.addWidget(QLabel("\"     P.A.:",self));
    self.wbpa = QLineEdit(self);
    lo1.addWidget(self.wbpa);
    lo1.addWidget(QLabel(u"\u00B0",self));
    for w in self.wbmaj,self.wbmin,self.wbpa:
      w.setValidator(QDoubleValidator(self));
    lo1 = QHBoxLayout();
    lo.addLayout(lo1);
    lo1.setContentsMargins(0,0,0,0);
    self.wfile_psf = FileSelector(self,label="Set restoring beam by fitting PSF image:",dialog_label="PSF FITS file",default_suffix="fits",file_types="FITS files (*.fits *.FITS)",file_mode=QFileDialog.ExistingFile);
    lo1.addSpacing(32);
    lo1.addWidget(self.wfile_psf);
    # selection only
    self.wselonly = QCheckBox("restore selected model sources only",self);
    lo.addWidget(self.wselonly );
    # OK/cancel buttons
    lo.addSpacing(10);
    lo2 = QHBoxLayout();
    lo.addLayout(lo2);
    lo2.setContentsMargins(0,0,0,0);
    lo2.setMargin(5);
    self.wokbtn = QPushButton("OK",self);
    self.wokbtn.setMinimumWidth(128);
    QObject.connect(self.wokbtn,SIGNAL("clicked()"),self.accept);
    self.wokbtn.setEnabled(False);
    cancelbtn = QPushButton("Cancel",self);
    cancelbtn.setMinimumWidth(128);
    QObject.connect(cancelbtn,SIGNAL("clicked()"),self.reject);
    lo2.addWidget(self.wokbtn);
    lo2.addStretch(1);
    lo2.addWidget(cancelbtn);
    self.setMinimumWidth(384);
    # signals
    QObject.connect(self.wfile_in,SIGNAL("filenameSelected"),self._fileSelected);
    QObject.connect(self.wfile_in,SIGNAL("filenameSelected"),self._inputFileSelected);
    QObject.connect(self.wfile_out,SIGNAL("filenameSelected"),self._fileSelected);
    QObject.connect(self.wfile_psf,SIGNAL("filenameSelected"),self._psfFileSelected);
    # internal state
    self.qerrmsg = QErrorMessage(self);

  def setModel (self,model):
    nsel = len([ src for src in model.sources if src.selected ]);
    self.wselonly.setVisible(nsel>0 and nsel<len(model.sources));
    self.model = model;
    self._fileSelected(None);

  def _fileSelected (self,filename):
    self.wokbtn.setEnabled(bool(self.wfile_in.filename() and self.wfile_out.filename()));
    
  def _inputFileSelected (self,filename):
    if filename:
      try:
        header = pyfits.open(filename)[0].header;
      except:
        self.qerrmsg.showMessage("Error reading FITS file %s: %s"%(filename,str(err)));
        self.wfile_in.setFilename("");
        return;
      # try to get beam extents
      gx,gy,grot = [ header.get(x,None) for x in 'BMAJ','BMIN','BPA' ];
      if all([x is not None for x in gx,gy,grot]):
        # if beam size is already set, ask before overwriting
        print [ str(x.text()) for x in self.wbmaj,self.wbmin,self.wbpa ]
Example #4
0
class RestoreImageDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Restore model into image")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # file selector
        self.wfile_in = FileSelector(self, label="Input FITS file:", dialog_label="Input FITS file",
                                     default_suffix="fits", file_types="FITS files (*.fits *.FITS)",
                                     file_mode=QFileDialog.ExistingFile)
        lo.addWidget(self.wfile_in)
        self.wfile_out = FileSelector(self, label="Output FITS file:", dialog_label="Output FITS file",
                                      default_suffix="fits", file_types="FITS files (*.fits *.FITS)",
                                      file_mode=QFileDialog.AnyFile)
        lo.addWidget(self.wfile_out)
        # beam size
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.addWidget(QLabel("Restoring beam FWHM, major axis:", self))
        self.wbmaj = QLineEdit(self)
        lo1.addWidget(self.wbmaj)
        lo1.addWidget(QLabel("\"     minor axis:", self))
        self.wbmin = QLineEdit(self)
        lo1.addWidget(self.wbmin)
        lo1.addWidget(QLabel("\"     P.A.:", self))
        self.wbpa = QLineEdit(self)
        lo1.addWidget(self.wbpa)
        lo1.addWidget(QLabel("\u00B0", self))
        for w in self.wbmaj, self.wbmin, self.wbpa:
            w.setValidator(QDoubleValidator(self))
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wfile_psf = FileSelector(self, label="Set restoring beam by fitting PSF image:",
                                      dialog_label="PSF FITS file", default_suffix="fits",
                                      file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile)
        lo1.addSpacing(32)
        lo1.addWidget(self.wfile_psf)
        # selection only
        self.wselonly = QCheckBox("restore selected model sources only", self)
        lo.addWidget(self.wselonly)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        QObject.connect(self.wfile_in, SIGNAL("filenameSelected"), self._fileSelected)
        QObject.connect(self.wfile_in, SIGNAL("filenameSelected"), self._inputFileSelected)
        QObject.connect(self.wfile_out, SIGNAL("filenameSelected"), self._fileSelected)
        QObject.connect(self.wfile_psf, SIGNAL("filenameSelected"), self._psfFileSelected)
        # internal state
        self.qerrmsg = QErrorMessage(self)

    def setModel(self, model):
        nsel = len([src for src in model.sources if src.selected])
        self.wselonly.setVisible(nsel > 0 and nsel < len(model.sources))
        self.model = model
        self._fileSelected(None)

    def _fileSelected(self, filename):
        self.wokbtn.setEnabled(bool(self.wfile_in.filename() and self.wfile_out.filename()))

    def _inputFileSelected(self, filename):
        if filename:
            try:
                header = pyfits.open(filename)[0].header
            except Exception as err:
                self.qerrmsg.showMessage("Error reading FITS file %s: %s" % (filename, str(err)))
                self.wfile_in.setFilename("")
                return
            # try to get beam extents
            gx, gy, grot = [header.get(x, None) for x in ('BMAJ', 'BMIN', 'BPA')]
            if all([x is not None for x in (gx, gy, grot)]):
                # if beam size is already set, ask before overwriting
                print([str(x.text()) for x in (self.wbmaj, self.wbmin, self.wbpa)])
                if any([bool(str(x.text())) for x in (self.wbmaj, self.wbmin, self.wbpa)]) and \
                        QMessageBox.question(self, "Set restoring beam",
                                             "Also reset restoring beam size from this FITS file?",
                                             QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes:
                    return
                self.wbmaj.setText("%.2f" % (gx * 3600))
                self.wbmin.setText("%.2f" % (gy * 3600))
                self.wbpa.setText("%.2f" % grot)

    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 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)
Example #5
0
class MakeBrickDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Convert sources to FITS brick")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # file selector
        self.wfile = FileSelector(self, label="FITS filename:", dialog_label="Output FITS file", default_suffix="fits",
                                  file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile)
        lo.addWidget(self.wfile)
        # reference frequency
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        label = QLabel("Frequency, MHz:", self)
        lo1.addWidget(label)
        tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated
    for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>"""
        self.wfreq = QLineEdit(self)
        self.wfreq.setValidator(QDoubleValidator(self))
        label.setToolTip(tip)
        self.wfreq.setToolTip(tip)
        lo1.addWidget(self.wfreq)
        # beam gain
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpb_apply = QCheckBox("Apply primary beam expression:", self)
        self.wpb_apply.setChecked(True)
        lo1.addWidget(self.wpb_apply)
        tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting
    them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding
    to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>"""
        self.wpb_exp = QLineEdit(self)
        self.wpb_apply.setToolTip(tip)
        self.wpb_exp.setToolTip(tip)
        lo1.addWidget(self.wpb_exp)
        # overwrite or add mode
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.woverwrite = QRadioButton("overwrite image", self)
        self.woverwrite.setChecked(True)
        lo1.addWidget(self.woverwrite)
        self.waddinto = QRadioButton("add into image", self)
        lo1.addWidget(self.waddinto)
        # add to model
        self.wadd = QCheckBox("Add resulting brick to sky model as a FITS image component", self)
        lo.addWidget(self.wadd)
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpad = QLineEdit(self)
        self.wpad.setValidator(QDoubleValidator(self))
        self.wpad.setText("1.1")
        lab = QLabel("...with padding factor:", self)
        lab.setToolTip("""<P>The padding factor determines the amount of null padding inserted around the image during
      the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show
      up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size.
      This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is
      perfectly fine.</P>""")
        self.wpad.setToolTip(lab.toolTip())
        QObject.connect(self.wadd, SIGNAL("toggled(bool)"), self.wpad.setEnabled)
        QObject.connect(self.wadd, SIGNAL("toggled(bool)"), lab.setEnabled)
        self.wpad.setEnabled(False)
        lab.setEnabled(False)
        lo1.addStretch(1)
        lo1.addWidget(lab, 0)
        lo1.addWidget(self.wpad, 1)
        self.wdel = QCheckBox("Remove from the sky model sources that go into the brick", self)
        lo.addWidget(self.wdel)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        QObject.connect(self.wfile, SIGNAL("filenameSelected"), self._fileSelected)
        # internal state
        self.qerrmsg = QErrorMessage(self)

    def setModel(self, model):
        self.model = model
        pb = self.model.primaryBeam()
        if pb:
            self.wpb_exp.setText(pb)
        else:
            self.wpb_apply.setChecked(False)
            self.wpb_exp.setText("")
        if model.filename():
            self._model_dir = os.path.dirname(os.path.abspath(model.filename()))
        else:
            self._model_dir = os.path.abspath('.')
        self.wfile.setDirectory(self._model_dir)
        self._fileSelected(self.wfile.filename(), quiet=True)

    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 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)
Example #6
0
class AddBrickDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.model = None
        self._model_dir = None
        self.setModal(modal)
        self.setWindowTitle("Add FITS brick")
        lo = QVBoxLayout(self)
        lo.setContentsMargins(10, 10, 10, 10)
        lo.setSpacing(5)
        # file selector
        self.wfile = FileSelector(self,
                                  label="FITS filename:",
                                  dialog_label="FITS file",
                                  default_suffix="fits",
                                  file_types="FITS files (*.fits *.FITS)",
                                  file_mode=QFileDialog.ExistingFile)
        lo.addWidget(self.wfile)
        # overwrite or add mode
        lo1 = QGridLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.addWidget(QLabel("Padding factor:", self), 0, 0)
        self.wpad = QLineEdit("2", self)
        self.wpad.setValidator(QDoubleValidator(self))
        lo1.addWidget(self.wpad, 0, 1)
        lo1.addWidget(QLabel("Assign source name:", self), 1, 0)
        self.wname = QLineEdit(self)
        lo1.addWidget(self.wname, 1, 1)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setContentsMargins(5, 5, 5, 5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        self.wokbtn.clicked.connect(self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        cancelbtn.clicked.connect(self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        self.wfile.filenameSelected.connect(self._fileSelected)
        # internal state
        self.qerrmsg = QErrorMessage(self)

    def setModel(self, model):
        self.model = model
        if model.filename():
            self._model_dir = os.path.dirname(os.path.abspath(
                model.filename()))
        else:
            self._model_dir = os.path.abspath('.')
        self.wfile.setDirectory(self._model_dir)
        self._fileSelected(self.wfile.filename(), quiet=True)

    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
        # 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):
                if os.path.exists(src.shape.filename) and os.path.samefile(
                        src.shape.filename, filename):
                    if not quiet:
                        QMessageBox.warning(
                            self, "Already in model",
                            "This FITS brick is already present in the model.")
                    self.wfile.setFilename('')
                    return None
        if not str(self.wname.text()):
            self.wname.setText(
                os.path.splitext(os.path.basename(str(filename)))[0])
        self.wokbtn.setEnabled(True)
        return filename

    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 as err:
            busy.reset_cursor()
            QMessageBox.warning(
                self, "Error reading FITS",
                "Error reading FITS file %s: %s" % (filename, str(err)))
            return
        # check name
        srcname = str(self.wname.text()) or os.path.splitext(
            os.path.basename(str(filename)))[0]
        if srcname in set([src.name for src in self.model.sources]):
            QMessageBox.warning(
                self, "Already in model",
                "<p>The model already contains a source named '%s'. Please select a different name.</p>"
                % srcname)
            return
        # get image parameters
        hdr = input_hdu.header
        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
        #    print ra0,dec0
        ra0, dec0 = wcs.pix2wcs(ra0, dec0)
        ra0 *= DEG
        dec0 *= DEG
        #    print ModelClasses.Position.ra_hms_static(ra0)
        #    print ModelClasses.Position.dec_sdms_static(dec0)
        sx, sy = wcs.getHalfSizeDeg()
        sx *= DEG
        sy *= DEG
        nx, ny = input_hdu.data.shape[-1:-3:-1]
        pos = ModelClasses.Position(ra0, dec0)
        flux = ModelClasses.Flux(max_flux)
        shape = ModelClasses.FITSImage(sx,
                                       sy,
                                       0,
                                       os.path.basename(filename),
                                       nx,
                                       ny,
                                       pad=float(str(self.wpad.text()) or "1"))
        img_src = SkyModel.Source(srcname, pos, flux, shape=shape)
        self.model.setSources(self.model.sources + [img_src])
        self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self)
        busy.reset_cursor()
        return QDialog.accept(self)
Example #7
0
class ExportKarmaDialog (QDialog):
  def __init__ (self,parent,modal=True,flags=Qt.WindowFlags()):
    QDialog.__init__(self,parent,flags);
    self.setModal(modal);
    self.setWindowTitle("Export Karma annotations");
    lo = QVBoxLayout(self);
    lo.setMargin(10);
    lo.setSpacing(5);
    # file selector
    self.wfile = FileSelector(self,label="Filename:",dialog_label="Karma annotations filename",default_suffix="ann",file_types="Karma annotations (*.ann)");
    lo.addWidget(self.wfile);
    # selected sources checkbox
    self.wsel = QCheckBox("selected sources only",self);
    lo.addWidget(self.wsel);
    # OK/cancel buttons
    lo.addSpacing(10);
    lo2 = QHBoxLayout();
    lo.addLayout(lo2);
    lo2.setContentsMargins(0,0,0,0);
    lo2.setMargin(5);
    self.wokbtn = QPushButton("OK",self);
    self.wokbtn.setMinimumWidth(128);
    QObject.connect(self.wokbtn,SIGNAL("clicked()"),self.accept);
    self.wokbtn.setEnabled(False);
    cancelbtn = QPushButton("Cancel",self);
    cancelbtn.setMinimumWidth(128);
    QObject.connect(cancelbtn,SIGNAL("clicked()"),self.reject);
    lo2.addWidget(self.wokbtn);
    lo2.addStretch(1);
    lo2.addWidget(cancelbtn);
    self.setMinimumWidth(384);
    # signals
    QObject.connect(self.wfile,SIGNAL("valid"),self.wokbtn.setEnabled);
    # internal state
    self.qerrmsg = QErrorMessage(self);
    self._model_filename = None;

  def setModel (self,model):
    self.model = model;
    # set the default annotations filename, whenever a new model filename is set
    filename = self.model.filename();
    if filename and filename != self._model_filename:
      self._model_filename = filename;
      self.wfile.setFilename(os.path.splitext(filename)[0] + ".ann");

  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;
    busy = None;
    self.parent().showMessage("Wrote Karma annotations for %d sources to file %s"%(len(sources),filename));
    return QDialog.accept(self);