Exemple #1
0
    def test_from_sequence(self):

        # Load a SExtractor catalog into memory and pass all of its stars to
        # Catalog.from_sequence(): both Catalogs must be equal. This is not the
        # case if the order of the stars passed to from_sequence() is altered,
        # of course, as tuples are compared position by position.

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        stars = catalog[:]

        identical = Catalog.from_sequence(*stars)
        self.assertEqual(type(identical), Catalog)
        self.assertEqual(identical, catalog)

        different = Catalog.from_sequence(*stars[::-1])
        self.assertEqual(type(different), Catalog)
        self.assertNotEqual(different, catalog)

        # Now create a second Catalog containing those stars whose magnitude is
        # greater than or equal to sixteen. There are nineteen of these stars,
        # but this number is anyway irrelevant for our purposes. What matters
        # is that all the stars passed to from_sequence() must also be in the
        # returned Catalog, in the same order.

        stars = [star for star in catalog if star.mag > 16]
        self.assertEqual(len(stars), 19)
        faint_catalog = Catalog.from_sequence(*stars)
        self.assertEqual(type(faint_catalog), Catalog)
        self.assertEqual(list(faint_catalog), list(stars))
Exemple #2
0
    def test_from_sequence(self):

        # Load a SExtractor catalog into memory and pass all of its stars to
        # Catalog.from_sequence(): both Catalogs must be equal. This is not the
        # case if the order of the stars passed to from_sequence() is altered,
        # of course, as tuples are compared position by position.

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        stars = catalog[:]

        identical = Catalog.from_sequence(*stars)
        self.assertEqual(type(identical), Catalog)
        self.assertEqual(identical, catalog)

        different = Catalog.from_sequence(*stars[::-1])
        self.assertEqual(type(different), Catalog)
        self.assertNotEqual(different, catalog)

        # Now create a second Catalog containing those stars whose magnitude is
        # greater than or equal to sixteen. There are nineteen of these stars,
        # but this number is anyway irrelevant for our purposes. What matters
        # is that all the stars passed to from_sequence() must also be in the
        # returned Catalog, in the same order.

        stars = [star for star in catalog if star.mag > 16]
        self.assertEqual(len(stars), 19)
        faint_catalog = Catalog.from_sequence(*stars)
        self.assertEqual(type(faint_catalog), Catalog)
        self.assertEqual(list(faint_catalog), list(stars))
Exemple #3
0
    def test_get_sky_coordinates(self):

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        self.assertEqual(len(catalog), 127)
        coordinates = catalog.get_sky_coordinates()

        # There should be as many Coordinates objects, and in the same order,
        # as sources there are in the SExtractor catalog. Therefore, the right
        # ascension and declination of the i-th Coordinates object must match
        # those of the i-th source in the Catalog.

        self.assertEqual(len(coordinates), len(catalog))
        for coord, star in zip(coordinates, catalog):
            self.assertEqual(coord.ra, star.alpha)
            self.assertEqual(coord.dec, star.delta)
Exemple #4
0
    def test_immutability(self):
        """ Make sure that the Catalog class is immutable """

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        with self.assertRaises(AttributeError):
            catalog.path = self.SAMPLE_INCOMPLETE_PATH
        with self.assertRaises(AttributeError):
            del catalog.path

        for index in xrange(len(catalog)):
            star = StarTest.random()
            with self.assertRaises(TypeError):
                catalog[index] = star
            with self.assertRaises(TypeError):
                del catalog[index]
Exemple #5
0
    def test_get_sky_coordinates(self):

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        self.assertEqual(len(catalog), 127)
        coordinates = catalog.get_sky_coordinates()

        # There should be as many Coordinates objects, and in the same order,
        # as sources there are in the SExtractor catalog. Therefore, the right
        # ascension and declination of the i-th Coordinates object must match
        # those of the i-th source in the Catalog.

        self.assertEqual(len(coordinates), len(catalog))
        for coord, star in zip(coordinates, catalog):
            self.assertEqual(coord.ra, star.alpha)
            self.assertEqual(coord.dec, star.delta)
Exemple #6
0
    def test_immutability(self):
        """ Make sure that the Catalog class is immutable """

        catalog = Catalog(self.SAMPLE_CATALOG_PATH)
        with self.assertRaises(AttributeError):
            catalog.path = self.SAMPLE_INCOMPLETE_PATH
        with self.assertRaises(AttributeError):
            del catalog.path

        for index in xrange(len(catalog)):
            star = StarTest.random()
            with self.assertRaises(TypeError):
                catalog[index] = star
            with self.assertRaises(TypeError):
                del catalog[index]
Exemple #7
0
    def test_init(self):

        path = self.SAMPLE_CATALOG_PATH
        catalog = Catalog(path)
        self.assertEqual(catalog.path, path)
        self.assertEqual(len(catalog), 127)

        star = catalog[0]
        self.assertEqual(star.x, 844.359)
        self.assertEqual(star.y, 434.109)
        self.assertEqual(star.alpha, 100.2910553)
        self.assertEqual(star.delta, +9.4697032)
        self.assertEqual(star.area, 8085)
        self.assertEqual(star.mag, 8.2460)
        self.assertFalse(star.saturated)
        # S/N = FLUX_ISO / FLUXERR_ISO = 8545557 / 12811.09
        self.assertAlmostEqual(star.snr, 667.043709786)
        self.assertAlmostEqual(star.fwhm, 12.668)  # FLUX_RADIUS x 2
        self.assertEqual(star.elongation, 1.562)

        star = catalog[68]
        self.assertEqual(star.x, 4.189)
        self.assertEqual(star.y, 1995.196)
        self.assertEqual(star.alpha, 100.1844554)
        self.assertEqual(star.delta, +9.2744660)
        self.assertEqual(star.area, 16)
        self.assertEqual(star.mag, 14.8506)
        self.assertFalse(star.saturated)
        # S/N = FLUX_ISO / FLUXERR_ISO = 10539.52 / 569.9098
        self.assertAlmostEqual(star.snr, 18.49331245)
        self.assertAlmostEqual(star.fwhm, 2.126)  # FLUX_RADIUS x 2
        self.assertEqual(star.elongation, 1.339)

        star = catalog[126]
        self.assertEqual(star.x, 107.033)
        self.assertEqual(star.y, 1953.500)
        self.assertEqual(star.alpha, 100.1971009)
        self.assertEqual(star.delta, +9.2796855)
        self.assertEqual(star.area, 10)
        self.assertEqual(star.mag, 15.9567)
        self.assertTrue(star.saturated)
        # S/N = FLUX_ISO / FLUXERR_ISO = 3833.773 / 450.5532
        self.assertAlmostEqual(star.snr, 8.509035115)
        self.assertAlmostEqual(star.fwhm, 2.412)  # FLUX_RADIUS x 2
        self.assertEqual(star.elongation, 1.613)

        # IOError is raised if the SExtractor catalog does not exist; in order
        # to get the path to a non-existent file, we use NamedTemporaryFile and
        # close the file (deleting it) immediately after.
        with tempfile.NamedTemporaryFile(suffix='.cat') as fd:
            pass
        self.assertFalse(os.path.exists(fd.name))
        self.assertRaises(IOError, Catalog, fd.name)

        # ValueError is raised if one of the required SExtractor parameters is
        # not in the catalog, or if it was not saved in the ASCII_HEAD format
        # and therefore there are no comment lines listing column labels, which
        # are needed in order to determine in which column each parameter is.
        self.assertRaises(ValueError, Catalog, self.SAMPLE_INCOMPLETE_PATH)
        self.assertRaises(ValueError, Catalog, self.SAMPLE_NOASCIIHEAD_PATH)
Exemple #8
0
    def test_flag_saturated(self):

        # Numbers in the [0, 255] range whose 3rd least significant bit is one
        saturated_flags = set([
            4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23, 28, 29, 30, 31, 36, 37,
            38, 39, 44, 45, 46, 47, 52, 53, 54, 55, 60, 61, 62, 63, 68, 69, 70,
            71, 76, 77, 78, 79, 84, 85, 86, 87, 92, 93, 94, 95, 100, 101, 102,
            103, 108, 109, 110, 111, 116, 117, 118, 119, 124, 125, 126, 127,
            132, 133, 134, 135, 140, 141, 142, 143, 148, 149, 150, 151, 156,
            157, 158, 159, 164, 165, 166, 167, 172, 173, 174, 175, 180, 181,
            182, 183, 188, 189, 190, 191, 196, 197, 198, 199, 204, 205, 206,
            207, 212, 213, 214, 215, 220, 221, 222, 223, 228, 229, 230, 231,
            236, 237, 238, 239, 244, 245, 246, 247, 252, 253, 254, 255
        ])

        for flag in xrange(0, 256):
            is_saturated = Catalog.flag_saturated(flag)
            if flag in saturated_flags:
                self.assertTrue(is_saturated)
            else:
                self.assertFalse(is_saturated)

        # Flags outside of the range raise ValueError
        self.assertRaises(ValueError, Catalog.flag_saturated, -2)
        self.assertRaises(ValueError, Catalog.flag_saturated, -1)
        self.assertRaises(ValueError, Catalog.flag_saturated, 256)
        self.assertRaises(ValueError, Catalog.flag_saturated, 257)
Exemple #9
0
    def test_flag_saturated(self):

        # Numbers in the [0, 255] range whose 3rd least significant bit is one
        saturated_flags = set([4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23, 28,
        29, 30, 31, 36, 37, 38, 39, 44, 45, 46, 47, 52, 53, 54, 55, 60, 61, 62,
        63, 68, 69, 70, 71, 76, 77, 78, 79, 84, 85, 86, 87, 92, 93, 94, 95,
        100, 101, 102, 103, 108, 109, 110, 111, 116, 117, 118, 119, 124, 125,
        126, 127, 132, 133, 134, 135, 140, 141, 142, 143, 148, 149, 150, 151,
        156, 157, 158, 159, 164, 165, 166, 167, 172, 173, 174, 175, 180, 181,
        182, 183, 188, 189, 190, 191, 196, 197, 198, 199, 204, 205, 206, 207,
        212, 213, 214, 215, 220, 221, 222, 223, 228, 229, 230, 231, 236, 237,
        238, 239, 244, 245, 246, 247, 252, 253, 254, 255])

        for flag in xrange(0, 256):
            is_saturated = Catalog.flag_saturated(flag)
            if flag in saturated_flags:
                self.assertTrue(is_saturated)
            else:
                self.assertFalse(is_saturated)

        # Flags outside of the range raise ValueError
        self.assertRaises(ValueError, Catalog.flag_saturated, -2)
        self.assertRaises(ValueError, Catalog.flag_saturated, -1)
        self.assertRaises(ValueError, Catalog.flag_saturated, 256)
        self.assertRaises(ValueError, Catalog.flag_saturated, 257)
Exemple #10
0
    def test_sextractor(self):

        # Note that, being a third-party program, we cannot write a unit test
        # to check that SExtractor works as it should (i.e., that it correctly
        # detects astronomical sources on FITS images): that is something that
        # Emmanuel Bertin, its developer, certainly takes care of. What we must
        # test for is whether astromatic.sextractor(), our Python wrapper to
        # call SExtractor, works as we expect and raises the appropriate errors
        # when something goes wrong. Nothing less, nothing more.

        # First, the most probable scenario: our wrapper should run SExtractor
        # on any FITS image that it receives (here we use some downloaded from
        # the STScI Digitized Sky Survey) and return the path to the output
        # catalog, which astromatic.Catalog should be always able to parse.

        kwargs = dict(stdout=open(os.devnull), stderr=open(os.devnull))
        for img_path in dss_images.TEST_IMAGES:
            catalog_path = astromatic.sextractor(img_path, **kwargs)
            try:
                Catalog(catalog_path)
            finally:
                os.unlink(catalog_path)

        # SExtractorNotInstalled must be raised if no SExtractor executable is
        # detected. In order to simulate this, mock os.environ and clear the
        # 'PATH' environment variable. In this manner, as the list of paths to
        # directories where executables may be found is empty, SExtractor (and
        # any other command) will appear as not installed on the system.

        environment_copy = copy.deepcopy(os.environ)
        with mock.patch.object(os, 'environ', environment_copy) as mocked:
            mocked['PATH'] = ''
            with self.assertRaises(astromatic.SExtractorNotInstalled):
                astromatic.sextractor(img_path)

        # IOError is raised if any of the four SExtractor configuration files
        # is not readable or does no exist. To test that, mock the module-level
        # variables so that they first refer to an unreadable file and later to
        # a nonexistent one.

        for variable in self.SEXTRACTOR_MODULE_VARS:

            path = eval('astromatic.%s' % variable)
            ext = os.path.splitext(path)[1]
            copy_path = get_nonexistent_path(ext=ext)
            shutil.copy2(path, copy_path)

            with mock.patch.object(astromatic, variable, copy_path):

                # chmod u-r
                mode = stat.S_IMODE(os.stat(copy_path)[stat.ST_MODE])
                mode ^= stat.S_IRUSR
                os.chmod(copy_path, mode)

                args = IOError, astromatic.sextractor, img_path
                self.assertFalse(os.access(copy_path, os.R_OK))
                self.assertRaises(*args)

                os.unlink(copy_path)
                self.assertFalse(os.path.exists(copy_path))
                self.assertRaises(*args)

        # SExtractorUpgradeRequired must be raised if the version of SExtractor
        # that is installed on the system is older than that defined in the
        # SEXTRACTOR_REQUIRED_VERSION module-level variable. We cannot (easily,
        # at least) change the version that is installed, but we can test for
        # this by changing the value of the required version, setting it to a
        # tuple that is greater than the installed version of SExtractor.

        # From, for example, (2, 8, 6) to (2, 8, 7)
        version = list(astromatic.sextractor_version())
        version[-1] += 1
        version = tuple(version)

        with mock.patch.object(astromatic, 'SEXTRACTOR_REQUIRED_VERSION',
                               version):
            with self.assertRaises(astromatic.SExtractorUpgradeRequired):
                astromatic.sextractor(img_path)

        # The SExtractorError exception is raised if anything goes wrong during
        # the execution of SExtractor (in more technical terms, if its return
        # code is other than zero). For example, the FITS image may not exist,
        # or a non-numerical value may be assigned to a parameter that expects
        # one, or we could try to run SExtractor on a FITS extension that does
        # not exist. These three are just examples: SExtractor may fail for
        # many different reasons that we cannot even foresee.

        # (1) Try to run SExtractor on a non-existent image
        kwargs = dict(stdout=open(os.devnull), stderr=open(os.devnull))
        nonexistent_path = get_nonexistent_path(ext='.fits')
        with self.assertRaises(astromatic.SExtractorError):
            astromatic.sextractor(nonexistent_path, **kwargs)

        # (2) The DETECT_MINAREA parameter (minimum number of pixels above the
        # threshold needed to trigger detection) expects an integer. Assigning
        # to it a string will cause SExtractor to complain ("keyword out of
        # range") and abort its execution.

        kwargs['options'] = dict(DETECT_MINAREA='Y')
        with self.assertRaises(astromatic.SExtractorError):
            astromatic.sextractor(img_path, **kwargs)
        del kwargs['options']

        # (3) Try to run SExtractor on a nonexistent FITS extension.
        hdulist = pyfits.open(img_path, mode='readonly')
        nextensions = len(hdulist)
        hdulist.close()

        kwargs['ext'] = nextensions + 1
        with self.assertRaises(astromatic.SExtractorError):
            astromatic.sextractor(img_path, **kwargs)

        # TypeError raised if 'options' is not a dictionary
        kwargs = dict(options=['DETECT_MINAREA', '5'])
        with self.assertRaises(TypeError):
            astromatic.sextractor(img_path, **kwargs)

        # ... or if any of its elements is not a string.
        kwargs['options'] = {'DETECT_MINAREA': 125}
        with self.assertRaises(TypeError):
            astromatic.sextractor(img_path, **kwargs)

        # TypeError also raised if 'ext' is not an integer...
        kwargs = dict(ext=0.56)
        with self.assertRaises(TypeError):
            astromatic.sextractor(img_path, **kwargs)

        # ... even if it is a float but has nothing after the decimal point
        # (for which the built-in is_integer() function would return True).
        kwargs = dict(ext=0.0)
        with self.assertRaises(TypeError):
            astromatic.sextractor(img_path, **kwargs)