Example #1
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)
Example #2
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)
Example #3
0
            # This point only reached if on-disk catalog can be reused
            msg = "%s: on-disk catalog exists and MD5 hashes match. Yay!"
            logging.debug(msg % self.path)

        except (KeyError, IOError, ValueError):

            msg = ("%s: could not reuse an existing, on-disk cached catalog; "
                   "SExtractor must be run") % self.path
            logging.debug(msg)

             # Redirect standard and error outputs to null device
            with open(os.devnull, 'wt') as fd:
                logging.info("%s: running SExtractor" % self.path)

                self.catalog_path = \
                    astromatic.sextractor(self.path, options = options,
                                          stdout = fd, stderr = fd)

                logging.debug("%s: SExtractor OK" % self.path)

                try:
                    # Update the FITS header with the path and MD5; give up
                    # silently in case we do not have permissions to do it
                    # (IOError) or if the length of the HIERARCH keyword, equal
                    # sign and value is longer than 80 characters (ValueError,
                    # see FITSImage.update_keyword() for details). The cast to
                    # str is needed because PyFITS has complained sometimes
                    # about "illegal values" if it receives a Unicode string.
                    self.update_keyword(keywords.sex_catalog, str(self.catalog_path))
                    self.update_keyword(keywords.sex_md5sum, sex_md5sum)
                except (IOError, ValueError):
                    pass
Example #4
0
            # This point only reached if on-disk catalog can be reused
            msg = "%s: on-disk catalog exists and MD5 hashes match. Yay!"
            logging.debug(msg % self.path)

        except (KeyError, IOError, ValueError):

            msg = ("%s: could not reuse an existing, on-disk cached catalog; "
                   "SExtractor must be run") % self.path
            logging.debug(msg)

            # Redirect standard and error outputs to null device
            with open(os.devnull, 'wt') as fd:
                logging.info("%s: running SExtractor" % self.path)

                self.catalog_path = \
                    astromatic.sextractor(self.path, options = options,
                                          stdout = fd, stderr = fd)

                logging.debug("%s: SExtractor OK" % self.path)

                try:
                    # Update the FITS header with the path and MD5; give up
                    # silently in case we do not have permissions to do it
                    # (IOError) or if the length of the HIERARCH keyword, equal
                    # sign and value is longer than 80 characters (ValueError,
                    # see FITSImage.update_keyword() for details). The cast to
                    # str is needed because PyFITS has complained sometimes
                    # about "illegal values" if it receives a Unicode string.
                    self.update_keyword(keywords.sex_catalog,
                                        str(self.catalog_path))
                    self.update_keyword(keywords.sex_md5sum, sex_md5sum)
                except (IOError, ValueError):