コード例 #1
0
ファイル: test_methods.py プロジェクト: dchecks/lemon
    def test_filter_close(self):

        fd = open(os.devnull, 'wt')
        regexp = "Keyword (?P<msg>EXPTIME) not found"
        args = fd, regexp, RuntimeWarning
        devnull_filter = methods.StreamToWarningFilter(*args)
        # Must close the underlying file object
        devnull_filter.close()
        self.assertTrue(fd.closed)
コード例 #2
0
    def run(self, annulus, dannulus, aperture, exptimek, cbox=0):
        """ Run IRAF's qphot on the FITS image.

        This method is a wrapper, equivalent to (1) running 'qphot' on a FITS
        image and (2) using 'txdump' in order to extract some fields from the
        resulting text database. This subroutine, therefore, allows to easily
        do photometry on a FITS image, storing as a sequence of QPhotResult
        objects the photometric measurements. The method returns the number
        of astronomical objects on which photometry has been done.

        No matter how convenient, QPhot.run() should still be seen as a
        low-level method: INDEF objects will have a magnitude and standard
        deviation of None, but it does not pay attention to the saturation
        levels -- the sole reason for this being that IRAF's qphot, per se,
        provides no way of knowing if one or more pixels in the aperture are
        above some saturation level. For this very reason, the recommended
        approach to doing photometry is to use photometry(), a convenience
        function defined below.

        In the first step, photometry is done, using qphot, on the astronomical
        objects whose coordinates have been listed, one per line, in the text
        file passed as an argument to QPhot.__init__(). The output of this IRAF
        task is saved to temporary file (a), an APPHOT text database from which
        'txdump' extracts the fields to another temporary file, (b). Then this
        file is parsed, the information of each of its lines, one per object,
        used in order to create a QPhotResult object. All previous photometric
        measurements are lost every time this method is run. All the temporary
        files (a, b) are guaranteed to be deleted on exit, even if an error is
        encountered.

        An important note: you may find extremely confusing that, although the
        input that this method accepts are celestial coordinates (the right
        ascension and declination of the astronomical objects to be measured),
        the resulting QPhotResult objects store the x- and y-coordinates of
        their centers. The reason for this is that IRAF's qphot supports
        'world' coordinates as the system of the input coordinates, but the
        output coordinate system options are 'logical', 'tv', and 'physical':
        http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?qphot

        The x- and y-coordinates of the centers of the astronomical objects may
        be reported as -1 if their celestial coordinates fall considerably off
        those of the FITS image on which photometry is done. This is caused by
        a bug in IRAF, which, as of v.2.16.1, outputs invalid floating-point
        numbers (such as '-299866.375-58') when the astronomical object is
        approximately >= 100 degrees off the image boundaries. In those cases,
        the fallback value of -1 does not give us any other information than
        that the object falls off the image. This must probably be one of the
        least important bugs ever, considering how wrong the input celestial
        coordinates must be.

        Bug report that we have submitted to the IRAF development team:
        [URL] http://iraf.net/forum/viewtopic.php?showtopic=1468373

        Arguments:
        annulus - the inner radius of the sky annulus, in pixels.
        dannulus - the width of the sky annulus, in pixels.
        aperture - the aperture radius, in pixels.
        exptimek - the image header keyword containing the exposure time, in
                   seconds. Needed by qphot in order to normalize the computed
                   magnitudes to an exposure time of one time unit. In case it
                   cannot be read from the FITS header, the MissingFITSKeyword
                   warning is issued and the default value of '' used instead.
                   Although non-fatal, this means that magnitudes will not be
                   normalized, which probably is not what you want.
        cbox - the width of the centering box, in pixels. Accurate centers for
               each astronomical object are computed using the centroid
               centering algorithm. This means that, unless this argument is
               zero (the default value), photometry is not done exactly on the
               specified coordinates, but instead where IRAF has determined
               that the actual, accurate center of each object is. This is
               usually a good thing, and helps improve the photometry.

        """

        self.clear()  # empty object

        try:
            # Temporary file to which the APPHOT text database produced by
            # qphot will be saved. Even if empty, it must be deleted before
            # calling qphot. Otherwise, an error message, stating that the
            # operation "would overwrite existing file", will be thrown.
            output_fd, qphot_output = \
                tempfile.mkstemp(prefix = os.path.basename(self.path),
                                 suffix = '.qphot_output', text = True)
            os.close(output_fd)
            os.unlink(qphot_output)

            # In case the FITS image does not contain the keyword for the
            # exposure time, qphot() shows a message such as: "Warning: Image
            # NGC2264-mosaic.fits Keyword:  EXPTIME not found". This, however,
            # is not a warning, but a simple message written to standard error.
            # Filter this stream so that, if this message is written, it is
            # captured and issued as a MissingFITSkeyword warning instead.

            # Note the two whitespaces before 'Keyword'
            regexp = ("Warning: Image (?P<msg>{0}  Keyword: {1} "
                      "not found)".format(self.path, exptimek))

            args = sys.stderr, regexp, MissingFITSKeyword
            stderr = methods.StreamToWarningFilter(*args)

            # Run qphot on the image and save the output to our temporary file.
            kwargs = dict(cbox=cbox,
                          annulus=annulus,
                          dannulus=dannulus,
                          aperture=aperture,
                          coords=self.coords_path,
                          output=qphot_output,
                          exposure=exptimek,
                          wcsin='world',
                          interactive='no',
                          Stderr=stderr)

            apphot.qphot(self.path, **kwargs)

            # Make sure the output was written to where we said
            assert os.path.exists(qphot_output)

            # Now extract the records from the APPHOT text database. We need
            # the path of another temporary file to which to save them. Even
            # if empty, we need to delete the temporary file created by
            # mkstemp(), as IRAF will not overwrite it.
            txdump_fd, txdump_output = \
                tempfile.mkstemp(prefix = os.path.basename(self.path),
                                 suffix ='.qphot_txdump', text = True)
            os.close(txdump_fd)
            os.unlink(txdump_output)

            # The type casting of Stdout to string is needed as txdump will not
            # work with unicode, if we happen to come across it: PyRAF requires
            # that redirection be to a file handle or string.

            txdump_fields = [
                'xcenter', 'ycenter', 'mag', 'sum', 'flux', 'stdev'
            ]
            pyraf.iraf.txdump(qphot_output,
                              fields=','.join(txdump_fields),
                              Stdout=str(txdump_output),
                              expr='yes')

            # Now open the output file again and parse the output of txdump,
            # creating a QPhotResult object for each record.
            with open(txdump_output, 'rt') as txdump_fd:
                for line in txdump_fd:

                    fields = line.split()

                    # As of IRAF v.2.16.1, the qphot task may output an invalid
                    # floating-point number (such as "-299866.375-58") when the
                    # coordinates of the object to be measured fall considerably
                    # off (>= ~100 degrees) the image. That raises ValueError
                    # ("invalid literal for float()") when we attempt to convert
                    # the output xcenter or ycenter to float. In those cases, we
                    # use -1 as the fallback value.

                    try:
                        xcenter_str = fields[0]
                        xcenter = float(xcenter_str)
                        msg = "%s: xcenter = %.8f" % (self.path, xcenter)
                        logging.debug(msg)
                    except ValueError, e:
                        msg = "%s: can't convert xcenter = '%s' to float (%s)"
                        logging.debug(msg % (self.path, xcenter_str, str(e)))
                        msg = "%s: xcenter set to -1 (fallback value)"
                        logging.debug(msg % self.path)
                        xcenter = -1

                    try:
                        ycenter_str = fields[1]
                        ycenter = float(ycenter_str)
                        msg = "%s: ycenter = %.8f" % (self.path, ycenter)
                        logging.debug(msg)
                    except ValueError, e:
                        msg = "%s: can't convert ycenter = '%s' to float (%s)"
                        logging.debug(msg % (self.path, ycenter_str, str(e)))
                        msg = "%s: ycenter set to -1 (fallback value)"
                        logging.debug(msg % self.path)
                        ycenter = -1

                    try:
                        mag_str = fields[2]
                        mag = float(mag_str)
                        msg = "%s: mag = %.5f" % (self.path, mag)
                        logging.debug(msg)
                    except ValueError:  # float("INDEF")
                        assert mag_str == 'INDEF'
                        msg = "%s: mag = None ('INDEF')" % self.path
                        logging.debug(msg)
                        mag = None

                    sum_ = float(fields[3])
                    msg = "%s: sum = %.5f" % (self.path, sum_)
                    logging.debug(msg)

                    flux = float(fields[4])
                    msg = "%s: flux = %.5f" % (self.path, flux)
                    logging.debug(msg)

                    try:
                        stdev_str = fields[5]
                        stdev = float(stdev_str)
                        msg = "%s: stdev = %.5f" % (self.path, stdev)
                        logging.debug(msg)
                    except ValueError:  # float("INDEF")
                        assert stdev_str == 'INDEF'
                        msg = "%s: stdev = None ('INDEF')" % self.path
                        logging.debug(msg)
                        stdev = None

                    args = xcenter, ycenter, mag, sum_, flux, stdev
                    self.append(QPhotResult(*args))
コード例 #3
0
ファイル: test_methods.py プロジェクト: dchecks/lemon
    def test_filter_stream(self):

        # A filter that, if the 'foa', 'fooa' or 'foooa' strings are matched,
        # issues UserWarning using the string as the warning message. If not,
        # writes the string to the output stream (here, a StringIO)

        output = StringIO.StringIO()
        expected_output = 'fo{1,3}a'
        regexp = '(?P<msg>{0})'.format(expected_output)
        category = UserWarning
        args = output, regexp, category
        stdout_filter = methods.StreamToWarningFilter(*args)

        with warnings.catch_warnings():
            warnings.filterwarnings('error')

            # 'fooa' matches the regexp, so issue the warning
            with self.assertRaisesRegexp(category, expected_output):
                stdout_filter.write("fooa")

            # 'spam' does not match, so write to the output stream
            str_ = "spam"
            stdout_filter.write(str_)
            self.assertEqual(output.getvalue(), str_)

        # A filter that, if "Warning: Keyword: EXPTIME not found" is matched,
        # issues RuntimeWarning with "EXPTIME not matched" as its message. If
        # not, writes the string to the output stream (again, a StringIO).

        output = StringIO.StringIO()
        expected_output = "EXPTIME not found"
        regexp = "Warning: Keyword: (?P<msg>{0})".format(expected_output)
        category = RuntimeWarning
        args = output, regexp, category
        stdout_filter = methods.StreamToWarningFilter(*args)

        with warnings.catch_warnings():
            warnings.filterwarnings('error')

            with self.assertRaisesRegexp(category, expected_output):
                str_ = "Warning: Keyword: EXPTIME not found"
                stdout_filter.write(str_)

            str_ = str_.replace('EXPTIME', 'OBJECT')
            stdout_filter.write(str_)
            self.assertEqual(output.getvalue(), str_)

        # The example mentioned in the class docstring

        output = StringIO.StringIO()
        expected_output = '2.19.5'
        regexp = 'v(?P<msg>(\d\.?)+)'
        category = UserWarning
        args = output, regexp, category
        stdout_filter = methods.StreamToWarningFilter(*args)

        with warnings.catch_warnings():
            warnings.filterwarnings('error')

            with self.assertRaisesRegexp(category, expected_output):
                stdout_filter.write('v2.19.5')

            str_ = "Nobody expects the Spanish inquisition"
            stdout_filter.write(str_)
            self.assertEqual(output.getvalue(), str_)

        # The regular expression does not include the mandatory 'msg' named
        # group, so IndexError is raised when the string is matched, as the
        # StreamToWarningFilter class tries to refer to a non-existent group.

        output = StringIO.StringIO()
        regexp = 'spam'
        args = output, regexp, UserWarning
        stdout_filter = methods.StreamToWarningFilter(*args)

        with self.assertRaisesRegexp(IndexError, "no such group"):
            stdout_filter.write("spam")