Esempio n. 1
0
    def do_stage(self, images):
        for image in images:
            image.header['RLEVEL'] = (self.pipeline_context.rlevel,
                                      'Reduction level')
            image.header['PIPEVER'] = (banzai.__version__, 'Pipeline version')

            if instantly_public(image.header['PROPID']):
                image.header['L1PUBDAT'] = (
                    image.header['DATE-OBS'],
                    '[UTC] Date the frame becomes public')
            else:
                # Wait a year
                date_observed = date_utils.parse_date_obs(
                    image.header['DATE-OBS'])
                next_year = date_observed + timedelta(days=365)
                image.header['L1PUBDAT'] = (
                    date_utils.date_obs_to_string(next_year),
                    '[UTC] Date the frame becomes public')
            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'rlevel', int(image.header['RLEVEL']))
            logs.add_tag(logging_tags, 'pipeline_version',
                         image.header['PIPEVER'])
            logs.add_tag(logging_tags, 'l1pubdat', image.header['L1PUBDAT'])
            self.logger.info('Updating header', extra=logging_tags)

        return images
Esempio n. 2
0
    def do_stage(self, images):
        for image in images:
            if len(image.data.shape) > 2:
                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
                n_amps = image.data.shape[0]
                crosstalk_matrix = np.identity(n_amps)
                for j in range(n_amps):
                    for i in range(n_amps):
                        if i != j:
                            crosstalk_keyword = 'CRSTLK{0}{1}'.format(i + 1, j + 1)
                            crosstalk_matrix[i, j] = -float(image.header[crosstalk_keyword])
                            logs.add_tag(logging_tags, crosstalk_keyword,
                                         image.header[crosstalk_keyword])
                self.logger.info('Removing crosstalk', extra=logging_tags)
                # Techinally, we should iterate this process because crosstalk doesn't
                # produce more crosstalk
                """This dot product is effectivly the following:
                coeffs = [[Q11, Q12, Q13, Q14],
                          [Q21, Q22, Q23, Q24],
                          [Q31, Q32, Q33, Q34],
                          [Q41, Q42, Q43, Q44]]

                The corrected data, D, from quadrant i is
                D1 = D1 - Q21 D2 - Q31 D3 - Q41 D4
                D2 = D2 - Q12 D1 - Q32 D3 - Q42 D4
                D3 = D3 - Q13 D1 - Q23 D2 - Q43 D4
                D4 = D4 - Q14 D1 - Q24 D2 - Q34 D3
                """
                image.data = np.dot(crosstalk_matrix.T, np.swapaxes(image.data, 0, 1))
        return images
Esempio n. 3
0
    def do_stage(self, images):
        for image in images:

            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'gain', image.gain)
            self.logger.info('Multiplying by gain', extra=logging_tags)

            if len(image.data.shape) > 2:
                n_amps = image.data.shape[0]
                gain = np.array(eval(image.gain))
                for i in range(n_amps):
                    image.data[i] *= gain[i]
                image.header['SATURATE'] *= min(gain)
                image.header['MAXLIN'] *= min(gain)
            else:
                image.data *= image.gain
                image.header['SATURATE'] *= image.gain
                image.header['MAXLIN'] *= 1.0

            image.gain = 1.0
            image.header['GAIN'] = 1.0
        return images
Esempio n. 4
0
def sinistro_mode_is_supported(image):
    """
    Check to make sure the Sinistro image was taken in a supported mode.

    Parameters
    ----------
    image: banzai.images.Image
           Sinistro image to check

    Returns
    -------
    supported: bool
               True if reduction is supported

    Notes
    -----
    Currently we only support 1x1 binning images.
    """
    # TODO Add support for other binnings
    supported = True

    tags = logs.image_config_to_tags(image, None)
    logs.add_tag(tags, 'filename', image.filename)
    if image.header['CCDSUM'] != '1 1':
        supported = False
        logger.error('Non-supported Sinistro mode', logging_tags=tags)
    if image.instrument not in crosstalk_coefficients.keys():
        supported = False
        logger.error('Crosstalk Coefficients missing!', extra=tags)

    return supported
Esempio n. 5
0
def image_has_valid_saturate_value(image):
    """
    Check if the image has a valid saturate value.

    Parameters
    ----------
    image: banzai.images.Image

    Returns
    -------
    valid: bool
           True if the image has a non-zero saturate value. False otherwise.

    Notes
    -----
    The saturate keyword being zero causes a lot of headaches so we should just dump
    the image if the saturate value is zero after we have fixed the typical incorrect values.
    """
    valid = True

    if float(image.header['SATURATE']) == 0.0:
        tags = logs.image_config_to_tags(image, None)
        logs.add_tag(tags, 'filename', image.filename)
        logger.error('SATURATE keyword cannot be zero', extra=tags)
        valid = False

    return valid
Esempio n. 6
0
    def do_stage(self, images):
        images_to_remove = []
        for image in images:

            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'gain', image.gain)
            self.logger.info('Multiplying by gain', extra=logging_tags)

            gain = image.gain
            if validate_gain(gain):
                self.logger.error('Gain missing. Rejecting image.',
                                  extra=logging_tags)
                images_to_remove.append(image)
            else:
                if len(image.data.shape) > 2:
                    n_amps = image.data.shape[0]
                    for i in range(n_amps):
                        image.data[i] *= gain[i]
                    image.header['SATURATE'] *= min(gain)
                    image.header['MAXLIN'] *= min(gain)
                else:
                    image.data *= image.gain
                    image.header['SATURATE'] *= image.gain
                    image.header['MAXLIN'] *= image.gain

                image.gain = 1.0
                image.header['GAIN'] = 1.0

        for image in images_to_remove:
            images.remove(image)

        return images
Esempio n. 7
0
    def check_exptime_value(self, image):
        """Logs an error if :
        -1) the keyword exptime is not higher than 0.0
        -2) the keyword exptime is equal to 0.0 and 'OBSTYPE' keyword is 'EXPOSE'

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """

        exptime_value = image.header['EXPTIME']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if exptime_value < 0.0:
            sentence = 'The header EXPTIME key got the unexpected value : negative value'
            self.logger.error(sentence, extra=logging_tags)
            return

        obstype = image.header['OBSTYPE']
        if (exptime_value == 0.0) & (obstype == 'EXPOSE'):
            sentence = 'The header EXPTIME key got the unexpected value : 0.0'
            self.logger.error(sentence, extra=logging_tags)
            return
Esempio n. 8
0
    def do_stage(self, images):
        images_to_remove = []
        for image in images:
            saturation_level = float(image.header['SATURATE'])
            saturated_pixels = image.data >= saturation_level
            total_pixels = image.data.size
            saturation_fraction = float(saturated_pixels.sum()) / total_pixels

            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'SATFRAC', saturation_fraction)
            self.logger.info('Measured saturation fraction.',
                             extra=logging_tags)
            if saturation_fraction >= self.threshold:
                self.logger.error('SATFRAC exceeds threshold.',
                                  extra=logging_tags)
                images_to_remove.append(image)
            else:
                image.header['SATFRAC'] = (
                    saturation_fraction,
                    "Fraction of Pixels that are Saturated")

        for image in images_to_remove:
            images.remove(image)

        return images
Esempio n. 9
0
    def do_stage(self, images):
        for image in images:
            if len(image.data.shape) > 2:

                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))

                n_amps = image.data.shape[0]
                nx, ny = get_mosaic_size(image, n_amps)
                mosaiced_data = np.zeros((ny, nx), dtype=np.float32)
                mosaiced_bpm = np.zeros((ny, nx), dtype=np.uint8)
                for i in range(n_amps):
                    datasec = image.header['DATASEC{0}'.format(i + 1)]
                    amp_slice = fits_utils.parse_region_keyword(datasec)
                    logs.add_tag(logging_tags, 'DATASEC{0}'.format(i + 1), datasec)

                    detsec = image.header['DETSEC{0}'.format(i + 1)]
                    mosaic_slice = fits_utils.parse_region_keyword(detsec)
                    logs.add_tag(logging_tags, 'DETSEC{0}'.format(i + 1), datasec)

                    mosaiced_data[mosaic_slice] = image.data[i][amp_slice]
                    mosaiced_bpm[mosaic_slice] = image.bpm[i][amp_slice]

                image.data = mosaiced_data
                image.bpm = mosaiced_bpm
                image.update_shape(nx, ny)
                image.header['NAXIS'] = 2
                image.header.pop('NAXIS3')

                self.logger.info('Mosaiced image', extra=logging_tags)
        return images
Esempio n. 10
0
def create_master_calibration_header(images):
    header = fits.Header()
    for h in images[0].header.keys():
        try:
            # Dump empty header keywords
            if len(h) > 0:
                header[h] = images[0].header[h]
        except ValueError as e:
            logging_tags = logs.image_config_to_tags(images[0], None)
            logs.add_tag(logging_tags, 'filename', images[0].filename)
            logger.error('Could not add keyword {0}'.format(h),
                         extra=logging_tags)
            continue

    header = sanitizeheader(header)

    observation_dates = [image.dateobs for image in images]
    mean_dateobs = date_utils.mean_date(observation_dates)

    header['DATE-OBS'] = date_utils.date_obs_to_string(mean_dateobs)

    header.add_history("Images combined to create master calibration image:")
    for image in images:
        header.add_history(image.filename)
    return header
Esempio n. 11
0
    def check_exptime_value(self, image):
        """Logs an error if :
        -1) the keyword exptime is not higher than 0.0
        -2) the keyword exptime is equal to 0.0 and 'OBSTYPE' keyword is 'EXPOSE'

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """

        exptime_value = image.header['EXPTIME']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if exptime_value < 0.0:
            sentence = 'The header EXPTIME key got the unexpected value : negative value'
            self.logger.error(sentence, extra=logging_tags)
            return

        obstype = image.header['OBSTYPE']
        if (exptime_value == 0.0) & (obstype == 'EXPOSE'):
            sentence = 'The header EXPTIME key got the unexpected value : 0.0'
            self.logger.error(sentence, extra=logging_tags)
            return
Esempio n. 12
0
    def do_stage(self, images):

        for i, image in enumerate(images):
            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

            # Save the catalog to a temporary file
            filename = os.path.basename(image.filename)
            logs.add_tag(logging_tags, 'filename', filename)

            # Skip the image if we don't have some kind of initial RA and Dec guess
            if np.isnan(image.ra) or np.isnan(image.dec):
                self.logger.error('Skipping WCS solution. No initial pointing guess from header.',
                                  extra=logging_tags)
                continue

            with tempfile.TemporaryDirectory() as tmpdirname:
                catalog_name = os.path.join(tmpdirname, filename.replace('.fits', '.cat.fits'))
                try:
                    image.write_catalog(catalog_name, nsources=40)
                except image_utils.MissingCatalogException:
                    image.header['WCSERR'] = (4, 'Error status of WCS fit. 0 for no error')
                    self.logger.error('No source catalog. Not attempting WCS solution',
                                      extra=logging_tags)
                    continue

                # Run astrometry.net
                wcs_name = os.path.join(tmpdirname, filename.replace('.fits', '.wcs.fits'))
                command = self.cmd.format(ra=image.ra, dec=image.dec, scale_low=0.9*image.pixel_scale,
                                          scale_high=1.1*image.pixel_scale, wcs_name=wcs_name,
                                          catalog_name=catalog_name, nx=image.nx, ny=image.ny)

                console_output = subprocess.check_output(shlex.split(command))
                self.logger.debug(console_output, extra=logging_tags)

                if os.path.exists(wcs_name):
                    # Copy the WCS keywords into original image
                    new_header = fits.getheader(wcs_name)

                    header_keywords_to_update = ['CTYPE1', 'CTYPE2', 'CRPIX1', 'CRPIX2', 'CRVAL1',
                                                 'CRVAL2', 'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2']
                    for keyword in header_keywords_to_update:
                        image.header[keyword] = new_header[keyword]

                    image.header['WCSERR'] = (0, 'Error status of WCS fit. 0 for no error')

                    # Update the RA and Dec header keywords
                    image.header['RA'], image.header['DEC'] = get_ra_dec_in_sexagesimal(image.header['CRVAL1'],
                                                                                        image.header['CRVAL2'])

                    # Clean up wcs file
                    os.remove(wcs_name)

                    # Add the RA and Dec values to the catalog
                    add_ra_dec_to_catalog(image)
                else:
                    image.header['WCSERR'] = (4, 'Error status of WCS fit. 0 for no error')

            logs.add_tag(logging_tags, 'WCSERR', image.header['WCSERR'])
            self.logger.info('Attempted WCS Solve', extra=logging_tags)
        return images
Esempio n. 13
0
    def do_stage(self, images):
        for image in images:
            if len(image.data.shape) > 2:

                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))

                n_amps = image.data.shape[0]
                nx, ny = get_mosaic_size(image, n_amps)
                mosaiced_data = np.zeros((ny, nx), dtype=np.float32)
                mosaiced_bpm = np.zeros((ny, nx), dtype=np.uint8)
                for i in range(n_amps):
                    datasec = image.header['DATASEC{0}'.format(i + 1)]
                    amp_slice = fits_utils.parse_region_keyword(datasec)
                    logs.add_tag(logging_tags, 'DATASEC{0}'.format(i + 1),
                                 datasec)

                    detsec = image.header['DETSEC{0}'.format(i + 1)]
                    mosaic_slice = fits_utils.parse_region_keyword(detsec)
                    logs.add_tag(logging_tags, 'DETSEC{0}'.format(i + 1),
                                 datasec)

                    mosaiced_data[mosaic_slice] = image.data[i][amp_slice]
                    mosaiced_bpm[mosaic_slice] = image.bpm[i][amp_slice]

                image.data = mosaiced_data
                image.bpm = mosaiced_bpm
                image.update_shape(nx, ny)
                image.header['NAXIS'] = 2
                image.header.pop('NAXIS3')

                self.logger.info('Mosaiced image', extra=logging_tags)
        return images
Esempio n. 14
0
def get_bpm(image, pipeline_context):
    bpm_filename = dbs.get_bpm(image.telescope_id,
                               image.ccdsum,
                               db_address=pipeline_context.db_address)
    if bpm_filename is None:
        bpm = None
        image.header['L1IDMASK'] = ('', 'Id. of mask file used')
    else:
        bpm_hdu = fits_utils.open_fits_file(bpm_filename)
        bpm_extensions = fits_utils.get_extensions_by_name(bpm_hdu, 'BPM')
        if len(bpm_extensions) > 1:
            extension_shape = bpm_extensions[0].data.shape
            bpm_shape = (len(bpm_extensions), extension_shape[0],
                         extension_shape[1])
            bpm = np.zeros(bpm_shape, dtype=np.uint8)
            for i, extension in enumerate(bpm_extensions):
                bpm[i, :, :] = extension.data[:, :]
        elif len(bpm_extensions) == 1:
            bpm = np.array(bpm_extensions[0].data, dtype=np.uint8)
        else:
            bpm = np.array(bpm_hdu[0].data, dtype=np.uint8)
        if not bpm_has_valid_size(bpm, image):
            tags = logs.image_config_to_tags(image, None)
            logs.add_tag(tags, 'filename', image.filename)
            logger.error('BPM shape mismatch', extra=tags)
            err_msg = 'BPM shape mismatch for {filename} ' \
                      '{site}/{instrument}'.format(filename=image.filename, site=image.site,
                                                   instrument=image.instrument)
            raise ValueError(err_msg)
        image.header['L1IDMASK'] = (os.path.basename(bpm_filename),
                                    'Id. of mask file used')

    return bpm
Esempio n. 15
0
def get_bpm(image, pipeline_context):
    bpm_filename = dbs.get_bpm(image.telescope_id, image.ccdsum,
                               db_address=pipeline_context.db_address)
    if bpm_filename is None:
        bpm = None
        image.header['L1IDMASK'] = ('', 'Id. of mask file used')
    else:
        bpm_hdu = fits_utils.open_fits_file(bpm_filename)
        bpm_extensions = fits_utils.get_extensions_by_name(bpm_hdu, 'BPM')
        if len(bpm_extensions) > 1:
            extension_shape = bpm_extensions[0].data.shape
            bpm_shape = (len(bpm_extensions), extension_shape[0], extension_shape[1])
            bpm = np.zeros(bpm_shape, dtype=np.uint8)
            for i, extension in enumerate(bpm_extensions):
                bpm[i, :, :] = extension.data[:, :]
        elif len(bpm_extensions) == 1:
            bpm = np.array(bpm_extensions[0].data, dtype=np.uint8)
        else:
            bpm = np.array(bpm_hdu[0].data, dtype=np.uint8)
        if not bpm_has_valid_size(bpm, image):
            tags = logs.image_config_to_tags(image, None)
            logs.add_tag(tags, 'filename', image.filename)
            logger.error('BPM shape mismatch', extra=tags)
            err_msg = 'BPM shape mismatch for {filename} ' \
                      '{site}/{instrument}'.format(filename=image.filename, site=image.site,
                                                   instrument=image.instrument)
            raise ValueError(err_msg)
        image.header['L1IDMASK'] = (os.path.basename(bpm_filename), 'Id. of mask file used')

    return bpm
Esempio n. 16
0
    def do_stage(self, images):
        if len(images) < self.min_images:
            # Do nothing
            self.logger.warning('Not enough images to combine.')
            return []
        else:
            image_config = image_utils.check_image_homogeneity(images)
            logging_tags = logs.image_config_to_tags(image_config, self.group_by_keywords)

            return self.make_master_calibration_frame(images, image_config, logging_tags)
Esempio n. 17
0
    def do_stage(self, images):
        if len(images) < self.min_images:
            # Do nothing
            self.logger.warning('Not enough images to combine.')
            return []
        else:
            image_config = image_utils.check_image_homogeneity(images)
            logging_tags = logs.image_config_to_tags(image_config, self.group_by_keywords)

            return self.make_master_calibration_frame(images, image_config, logging_tags)
Esempio n. 18
0
    def do_stage(self, images):

        for image in images:

            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'trimsec', image.header['TRIMSEC'])
            self.logger.info('Trimming image', extra=logging_tags)

            nx, ny = _trim_image(image)
            image.update_shape(nx, ny)
        return images
Esempio n. 19
0
    def writeto(self, filename, fpack=False):
        image_hdu = fits.PrimaryHDU(self.data.astype(np.float32),
                                    header=self.header)
        image_hdu.header['BITPIX'] = -32
        image_hdu.header['BSCALE'] = 1.0
        image_hdu.header['BZERO'] = 0.0
        image_hdu.header['SIMPLE'] = True
        image_hdu.header['EXTEND'] = True
        image_hdu.name = 'SCI'
        hdu_list = [image_hdu]
        if self.catalog is not None:
            table_hdu = fits_utils.table_to_fits(self.catalog)
            table_hdu.name = 'CAT'
            hdu_list.append(table_hdu)
        if self.bpm is not None:
            bpm_hdu = fits.ImageHDU(self.bpm.astype(np.uint8))
            bpm_hdu.name = 'BPM'
            hdu_list.append(bpm_hdu)

        hdu_list = fits.HDUList(hdu_list)
        try:
            hdu_list.verify(option='exception')
        except fits.VerifyError as fits_error:
            logging_tags = logs.image_config_to_tags(self, None)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(self.filename))
            logger.warn(
                'Error in FITS Verification. {0}. Attempting fix.'.format(
                    fits_error),
                extra=logging_tags)
            try:
                hdu_list.verify(option='silentfix+exception')
            except fits.VerifyError as fix_attempt_error:
                logger.error('Could not repair FITS header. {0}'.format(
                    fix_attempt_error),
                             extra=logging_tags)

        with tempfile.TemporaryDirectory() as temp_directory:
            base_filename = os.path.basename(filename)
            hdu_list.writeto(os.path.join(temp_directory, base_filename),
                             overwrite=True,
                             output_verify='fix+warn')
            if fpack:
                filename += '.fz'
                if os.path.exists(filename):
                    os.remove(filename)
                os.system('fpack -q 64 {temp_directory}/{basename}'.format(
                    temp_directory=temp_directory, basename=base_filename))
                base_filename += '.fz'
                self.filename += '.fz'
            shutil.move(os.path.join(temp_directory, base_filename), filename)
Esempio n. 20
0
    def do_stage(self, images):
        if len(images) == 0:
            # Abort!
            return []
        else:
            image_config = image_utils.check_image_homogeneity(images)
            logging_tags = logs.image_config_to_tags(image_config, self.group_by_keywords)
            master_calibration_filename = self.get_calibration_filename(images[0])

            if master_calibration_filename is None:
                self.logger.error('Master Calibration file does not exist for {stage}'.format(stage=self.stage_name),
                                  extra=logging_tags)
                raise MasterCalibrationDoesNotExist

            master_calibration_image = Image(self.pipeline_context,
                                             filename=master_calibration_filename)
            return self.apply_master_calibration(images, master_calibration_image, logging_tags)
Esempio n. 21
0
 def do_stage(self, images):
     for image in images:
         if len(image.data.shape) > 2:
             logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
             logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
             n_amps = image.data.shape[0]
             crosstalk_matrix = np.identity(n_amps)
             for j in range(n_amps):
                 for i in range(n_amps):
                     if i != j:
                         crosstalk_keyword = 'CRSTLK{0}{1}'.format(i + 1, j + 1)
                         crosstalk_matrix[j, i] = -float(image.header[crosstalk_keyword])
                         logs.add_tag(logging_tags, crosstalk_keyword,
                                      image.header[crosstalk_keyword])
             self.logger.info('Removing crosstalk', extra=logging_tags)
             image.data = np.dot(crosstalk_matrix, np.swapaxes(image.data, 0, 1))
     return images
Esempio n. 22
0
    def do_stage(self, images):
        if len(images) == 0:
            # Abort!
            return []
        else:
            image_config = image_utils.check_image_homogeneity(images)
            logging_tags = logs.image_config_to_tags(image_config, self.group_by_keywords)
            master_calibration_filename = self.get_calibration_filename(images[0])

            if master_calibration_filename is None:
                self.logger.error('Master Calibration file does not exist for {stage}'.format(stage=self.stage_name),
                                  extra=logging_tags)
                raise MasterCalibrationDoesNotExist

            master_calibration_image = Image(self.pipeline_context,
                                             filename=master_calibration_filename)
            return self.apply_master_calibration(images, master_calibration_image, logging_tags)
Esempio n. 23
0
    def writeto(self, filename, fpack=False):
        image_hdu = fits.PrimaryHDU(self.data.astype(np.float32), header=self.header)
        image_hdu.header['BITPIX'] = -32
        image_hdu.header['BSCALE'] = 1.0
        image_hdu.header['BZERO'] = 0.0
        image_hdu.header['SIMPLE'] = True
        image_hdu.header['EXTEND'] = True
        image_hdu.name = 'SCI'
        hdu_list = [image_hdu]
        if self.catalog is not None:
            table_hdu = fits_utils.table_to_fits(self.catalog)
            table_hdu.name = 'CAT'
            hdu_list.append(table_hdu)
        if self.bpm is not None:
            bpm_hdu = fits.ImageHDU(self.bpm.astype(np.uint8))
            bpm_hdu.name = 'BPM'
            hdu_list.append(bpm_hdu)

        hdu_list = fits.HDUList(hdu_list)
        try:
            hdu_list.verify(option='exception')
        except fits.VerifyError as fits_error:
            logging_tags = logs.image_config_to_tags(self, None)
            logs.add_tag(logging_tags, 'filename', os.path.basename(self.filename))
            logger.warn('Error in FITS Verification. {0}. Attempting fix.'.format(fits_error),
                        extra=logging_tags)
            try:
                hdu_list.verify(option='silentfix+exception')
            except fits.VerifyError as fix_attempt_error:
                logger.error('Could not repair FITS header. {0}'.format(fix_attempt_error),
                             extra=logging_tags)

        with tempfile.TemporaryDirectory() as temp_directory:
            base_filename = os.path.basename(filename)
            hdu_list.writeto(os.path.join(temp_directory, base_filename), overwrite=True,
                             output_verify='fix+warn')
            if fpack:
                filename += '.fz'
                if os.path.exists(filename):
                    os.remove(filename)
                os.system('fpack -q 64 {temp_directory}/{basename}'.format(temp_directory=temp_directory,
                                                                           basename=base_filename))
                base_filename += '.fz'
                self.filename += '.fz'
            shutil.move(os.path.join(temp_directory, base_filename), filename)
Esempio n. 24
0
    def do_stage(self, images):
        images_to_remove = []
        for image in images:
            npixels = np.product(image.data.shape)
            fraction_1000s = float(np.sum(image.data == 1000)) / npixels
            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'FRAC1000', fraction_1000s)

            if fraction_1000s > self.threshold:
                self.logger.error('Image is mostly 1000s. Rejecting image', extra=logging_tags)
                images_to_remove.append(image)
            else:
                self.logger.info('Measuring fraction of 1000s.', extra=logging_tags)
        for image in images_to_remove:
            images.remove(image)

        return images
Esempio n. 25
0
    def check_dec_range(self, image):
        """Logs an error if the keyword declination is not inside
            the expected range (-90<dec<90 degrees) in the image header.

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """

        dec_value = image.header['CRVAL2']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if (dec_value > 90) | (dec_value < -90):
            sentence = 'The header CRVAL2 key got the unexpected value : ' + str(dec_value)
            self.logger.error(sentence, extra=logging_tags)
Esempio n. 26
0
File: qc.py Progetto: Fingel/banzai
    def do_stage(self, images):
        for image in images:
            saturation_level = float(image.header['SATURATE'])
            saturated_pixels = image.data >= saturation_level
            total_pixels = image.data.size
            saturation_fraction = float(saturated_pixels.sum()) / total_pixels

            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'SATFRAC', saturation_fraction)
            self.logger.info('Measured saturation fraction.', extra=logging_tags)
            if saturation_fraction >= self.threshold:
                self.logger.error('SATFRAC exceeds threshold.', extra=logging_tags)
                images.remove(image)
            else:
                image.header['SATFRAC'] = (saturation_fraction, "Fraction of Pixels that are Saturated")

        return images
Esempio n. 27
0
    def do_stage(self, images):

        for image in images:
            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))

            # Subtract the overscan if it exists
            if len(image.data.shape) > 2:
                for i in range(image.data.shape[0]):
                    overscan_level = _subtract_overscan_3d(image, i)
                    logs.add_tag(logging_tags, 'OVERSCN{0}'.format(i + 1), overscan_level)
                    self.logger.info('Subtracting overscan', extra=logging_tags)
            else:
                overscan_level = _subtract_overscan_2d(image)
                logs.add_tag(logging_tags, 'OVERSCAN', overscan_level)
                self.logger.info('Subtracting overscan', extra=logging_tags)

        return images
Esempio n. 28
0
    def writeto(self, filename, fpack=False):
        image_hdu = fits.PrimaryHDU(self.data.astype(np.float32),
                                    header=self.header)
        image_hdu.header['BITPIX'] = -32
        image_hdu.header['BSCALE'] = 1.0
        image_hdu.header['BZERO'] = 0.0
        image_hdu.header['SIMPLE'] = True
        image_hdu.header['EXTEND'] = True
        image_hdu.update_ext_name('SCI')
        hdu_list = [image_hdu]
        if self.catalog is not None:
            table_hdu = fits_utils.table_to_fits(self.catalog)
            table_hdu.update_ext_name('CAT')
            hdu_list.append(table_hdu)
        if self.bpm is not None:
            bpm_hdu = fits.ImageHDU(self.bpm.astype(np.uint8))
            bpm_hdu.update_ext_name('BPM')
            hdu_list.append(bpm_hdu)

        hdu_list = fits.HDUList(hdu_list)
        try:
            hdu_list.verify(option='exception')
        except fits.VerifyError as fits_error:
            logging_tags = logs.image_config_to_tags(self, None)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(self.filename))
            logger.warn(
                'Error in FITS Verification. {0}. Attempting fix.'.format(
                    fits_error),
                extra=logging_tags)
            try:
                hdu_list.verify(option='silentfix+exception')
            except fits.VerifyError as fix_attempt_error:
                logger.error('Could not repair FITS header. {0}'.format(
                    fix_attempt_error),
                             extra=logging_tags)

        hdu_list.writeto(filename, clobber=True, output_verify='fix+warn')
        if fpack:
            if os.path.exists(filename + '.fz'):
                os.remove(filename + '.fz')
            os.system('fpack -q 64 {0}'.format(filename))
            os.remove(filename)
            self.filename += '.fz'
Esempio n. 29
0
    def check_header_keyword_present(self, keyword, image):
        """ Logs an error if the keyword is not in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """
        header = image.header

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if keyword not in header:
            sentence = 'The header key ' + keyword + ' is not in image header!'

            self.logger.error(sentence, extra=logging_tags)
Esempio n. 30
0
def save_pipeline_metadata(image, pipeline_context):
    image.header['RLEVEL'] = (pipeline_context.rlevel, 'Reduction level')
    image.header['PIPEVER'] = (banzai.__version__, 'Pipeline version')

    if file_utils.instantly_public(image.header['PROPID']):
        image.header['L1PUBDAT'] = (image.header['DATE-OBS'],
                                    '[UTC] Date the frame becomes public')
    else:
        # Wait a year
        date_observed = date_utils.parse_date_obs(image.header['DATE-OBS'])
        next_year = date_observed + timedelta(days=365)
        image.header['L1PUBDAT'] = (date_utils.date_obs_to_string(next_year),
                                    '[UTC] Date the frame becomes public')
    logging_tags = logs.image_config_to_tags(image, None)
    logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
    logs.add_tag(logging_tags, 'rlevel', int(image.header['RLEVEL']))
    logs.add_tag(logging_tags, 'pipeline_version', image.header['PIPEVER'])
    logs.add_tag(logging_tags, 'l1pubdat', image.header['L1PUBDAT'])
    logger.info('Updating header', extra=logging_tags)
Esempio n. 31
0
    def check_header_keyword_present(self, keyword, image):
        """ Logs an error if the keyword is not in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """
        header = image.header

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if keyword not in header:
            sentence = 'The header key ' + keyword + ' is not in image header!'

            self.logger.error(sentence, extra=logging_tags)
Esempio n. 32
0
    def check_ra_range(self, image):
        """ Logs an error if the keyword right_ascension is not inside
            the expected range (0<ra<360 degrees) in the image header.

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """
        ra_value = image.header['CRVAL1']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if isinstance(ra_value, float):

            if (ra_value > 360) | (ra_value < 0):
                sentence = 'The header CRVAL1 key got the unexpected value : ' + str(ra_value)
                self.logger.error(sentence, extra=logging_tags)
Esempio n. 33
0
    def check_dec_range(self, image):
        """Logs an error if the keyword declination is not inside
            the expected range (-90<dec<90 degrees) in the image header.

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """

        dec_value = image.header['CRVAL2']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if (dec_value > 90) | (dec_value < -90):
            sentence = 'The header CRVAL2 key got the unexpected value : ' + str(
                dec_value)
            self.logger.error(sentence, extra=logging_tags)
Esempio n. 34
0
    def check_ra_range(self, image):
        """ Logs an error if the keyword right_ascension is not inside
            the expected range (0<ra<360 degrees) in the image header.

        Parameters
        ----------
        image : object
                a  banzais.image.Image object.

        """
        ra_value = image.header['CRVAL1']

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if isinstance(ra_value, float):

            if (ra_value > 360) | (ra_value < 0):
                sentence = 'The header CRVAL1 key got the unexpected value : ' + str(
                    ra_value)
                self.logger.error(sentence, extra=logging_tags)
Esempio n. 35
0
def _trim_image(image):
    trimsec = fits_utils.parse_region_keyword(image.header['TRIMSEC'])

    if trimsec is not None:
        image.data = image.data[trimsec]
        image.bpm = image.bpm[trimsec]

        # Update the NAXIS and CRPIX keywords
        image.header['NAXIS1'] = trimsec[1].stop - trimsec[1].start
        image.header['NAXIS2'] = trimsec[0].stop - trimsec[0].start
        image.header['CRPIX1'] -= trimsec[1].start
        image.header['CRPIX2'] -= trimsec[0].start

        image.header['L1STATTR'] = (1, 'Status flag for overscan trimming')
    else:
        logging_tags = logs.image_config_to_tags(image, None)
        logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
        logs.add_tag(logging_tags, 'trimsec', image.header['TRIMSEC'])
        logger.warning('TRIMSEC was not defined.', extra=logging_tags)
        image.header['L1STATTR'] = (0, 'Status flag for overscan trimming')
    return image.header['NAXIS1'], image.header['NAXIS2']
Esempio n. 36
0
    def do_stage(self, images):
        images_to_remove = []
        for image in images:
            npixels = np.product(image.data.shape)
            fraction_1000s = float(np.sum(image.data == 1000)) / npixels
            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'FRAC1000', fraction_1000s)

            if fraction_1000s > self.threshold:
                self.logger.error('Image is mostly 1000s. Rejecting image',
                                  extra=logging_tags)
                images_to_remove.append(image)
            else:
                self.logger.info('Measuring fraction of 1000s.',
                                 extra=logging_tags)
        for image in images_to_remove:
            images.remove(image)

        return images
Esempio n. 37
0
    def writeto(self, filename, fpack=False):
        image_hdu = fits.PrimaryHDU(self.data.astype(np.float32), header=self.header)
        image_hdu.header['BITPIX'] = -32
        image_hdu.header['BSCALE'] = 1.0
        image_hdu.header['BZERO'] = 0.0
        image_hdu.header['SIMPLE'] = True
        image_hdu.header['EXTEND'] = True
        image_hdu.update_ext_name('SCI')
        hdu_list = [image_hdu]
        if self.catalog is not None:
            table_hdu = fits_utils.table_to_fits(self.catalog)
            table_hdu.update_ext_name('CAT')
            hdu_list.append(table_hdu)
        if self.bpm is not None:
            bpm_hdu = fits.ImageHDU(self.bpm.astype(np.uint8))
            bpm_hdu.update_ext_name('BPM')
            hdu_list.append(bpm_hdu)

        hdu_list = fits.HDUList(hdu_list)
        try:
            hdu_list.verify(option='exception')
        except fits.VerifyError as fits_error:
            logging_tags = logs.image_config_to_tags(self, None)
            logs.add_tag(logging_tags, 'filename', os.path.basename(self.filename))
            logger.warn('Error in FITS Verification. {0}. Attempting fix.'.format(fits_error),
                        extra=logging_tags)
            try:
                hdu_list.verify(option='silentfix+exception')
            except fits.VerifyError as fix_attempt_error:
                logger.error('Could not repair FITS header. {0}'.format(fix_attempt_error),
                             extra=logging_tags)

        hdu_list.writeto(filename, clobber=True, output_verify='fix+warn')
        if fpack:
            if os.path.exists(filename + '.fz'):
                os.remove(filename + '.fz')
            os.system('fpack -q 64 {0}'.format(filename))
            os.remove(filename)
            self.filename += '.fz'
Esempio n. 38
0
    def check_header_na(self, keyword, image):
        """ Logs an error if the keyword is 'N/A' instead of the
            expected type in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """

        header_value = image.header[keyword]

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if isinstance(header_value, str):

            if 'N/A' in header_value:
                sentence = 'The header key ' + keyword + ' got the unexpected value : N/A'
                self.logger.error(sentence, extra=logging_tags)
Esempio n. 39
0
    def check_header_na(self, keyword, image):
        """ Logs an error if the keyword is 'N/A' instead of the
            expected type in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """

        header_value = image.header[keyword]

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if isinstance(header_value, str):

            if 'N/A' in header_value:
                sentence = 'The header key ' + keyword + ' got the unexpected value : N/A'
                self.logger.error(sentence, extra=logging_tags)
Esempio n. 40
0
 def do_stage(self, images):
     for image in images:
         if len(image.data.shape) > 2:
             logging_tags = logs.image_config_to_tags(
                 image, self.group_by_keywords)
             logs.add_tag(logging_tags, 'filename',
                          os.path.basename(image.filename))
             n_amps = image.data.shape[0]
             crosstalk_matrix = np.identity(n_amps)
             for j in range(n_amps):
                 for i in range(n_amps):
                     if i != j:
                         crosstalk_keyword = 'CRSTLK{0}{1}'.format(
                             i + 1, j + 1)
                         crosstalk_matrix[j, i] = -float(
                             image.header[crosstalk_keyword])
                         logs.add_tag(logging_tags, crosstalk_keyword,
                                      image.header[crosstalk_keyword])
             self.logger.info('Removing crosstalk', extra=logging_tags)
             image.data = np.dot(crosstalk_matrix,
                                 np.swapaxes(image.data, 0, 1))
     return images
Esempio n. 41
0
    def do_stage(self, images):

        for image in images:
            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename',
                         os.path.basename(image.filename))

            # Subtract the overscan if it exists
            if len(image.data.shape) > 2:
                for i in range(image.data.shape[0]):
                    overscan_level = _subtract_overscan_3d(image, i)
                    logs.add_tag(logging_tags, 'OVERSCN{0}'.format(i + 1),
                                 overscan_level)
                    self.logger.info('Subtracting overscan',
                                     extra=logging_tags)
            else:
                overscan_level = _subtract_overscan_2d(image)
                logs.add_tag(logging_tags, 'OVERSCAN', overscan_level)
                self.logger.info('Subtracting overscan', extra=logging_tags)

        return images
Esempio n. 42
0
    def do_stage(self, images):
        for image in images:

            logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
            logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
            logs.add_tag(logging_tags, 'gain', image.gain)
            self.logger.info('Multiplying by gain', extra=logging_tags)

            if len(image.data.shape) > 2:
                n_amps = image.data.shape[0]
                gain = np.array(eval(image.gain))
                for i in range(n_amps):
                    image.data[i] *= gain[i]
                image.header['SATURATE'] *= min(gain)
                image.header['MAXLIN'] *= min(gain)
            else:
                image.data *= image.gain
                image.header['SATURATE'] *= image.gain
                image.header['MAXLIN'] *= image.gain

            image.gain = 1.0
            image.header['GAIN'] = 1.0
        return images
Esempio n. 43
0
    def check_header_format(self, keyword, image):
        """ Logs an error if the keyword is not the expected type
            in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """

        header_value = image.header[keyword]

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if not isinstance(header_value, self.header_expected_format[keyword]):
            sentence = ('The header key ' + keyword + ' got an unexpected format : ' + type(
                header_value).__name__ + ' in place of ' + self.header_expected_format[
                    keyword].__name__)

            self.logger.error(sentence, extra=logging_tags)
Esempio n. 44
0
def create_master_calibration_header(images):
    header = fits.Header()
    for h in images[0].header.keys():
        try:
            # Dump empty header keywords
            if len(h) > 0:
                header[h] = images[0].header[h]
        except ValueError as e:
            logging_tags = logs.image_config_to_tags(images[0], None)
            logs.add_tag(logging_tags, 'filename', images[0].filename)
            logger.error('Could not add keyword {0}'.format(h), extra=logging_tags)
            continue

    header = sanitizeheader(header)

    observation_dates = [image.dateobs for image in images]
    mean_dateobs = date_utils.mean_date(observation_dates)

    header['DATE-OBS'] = date_utils.date_obs_to_string(mean_dateobs)

    header.add_history("Images combined to create master calibration image:")
    for image in images:
        header.add_history(image.filename)
    return header
Esempio n. 45
0
    def do_stage(self, images):
        for image in images:
            if len(image.data.shape) > 2:
                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))
                n_amps = image.data.shape[0]
                crosstalk_matrix = np.identity(n_amps)
                for j in range(n_amps):
                    for i in range(n_amps):
                        if i != j:
                            crosstalk_keyword = 'CRSTLK{0}{1}'.format(
                                i + 1, j + 1)
                            crosstalk_matrix[i, j] = -float(
                                image.header[crosstalk_keyword])
                            logs.add_tag(logging_tags, crosstalk_keyword,
                                         image.header[crosstalk_keyword])
                self.logger.info('Removing crosstalk', extra=logging_tags)
                # Techinally, we should iterate this process because crosstalk doesn't
                # produce more crosstalk
                """This dot product is effectivly the following:
                coeffs = [[Q11, Q12, Q13, Q14],
                          [Q21, Q22, Q23, Q24],
                          [Q31, Q32, Q33, Q34],
                          [Q41, Q42, Q43, Q44]]

                The corrected data, D, from quadrant i is
                D1 = D1 - Q21 D2 - Q31 D3 - Q41 D4
                D2 = D2 - Q12 D1 - Q32 D3 - Q42 D4
                D3 = D3 - Q13 D1 - Q23 D2 - Q43 D4
                D4 = D4 - Q14 D1 - Q24 D2 - Q34 D3
                """
                image.data = np.dot(crosstalk_matrix.T,
                                    np.swapaxes(image.data, 0, 1))
        return images
Esempio n. 46
0
    def check_header_format(self, keyword, image):
        """ Logs an error if the keyword is not the expected type
            in the image header.

        Parameters
        ----------
        keyword : str
                  the keyword of interest
        image : object
                a  banzais.image.Image object.

        """

        header_value = image.header[keyword]

        logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)

        if not isinstance(header_value, self.header_expected_format[keyword]):
            sentence = ('The header key ' + keyword +
                        ' got an unexpected format : ' +
                        type(header_value).__name__ + ' in place of ' +
                        self.header_expected_format[keyword].__name__)

            self.logger.error(sentence, extra=logging_tags)
Esempio n. 47
0
 def run_stage(self, image_set, image_config):
     image_set = list(image_set)
     tags = logs.image_config_to_tags(image_set[0], self.group_by_keywords)
     self.logger.info('Running {0}'.format(self.stage_name), extra=tags)
     return self.do_stage(image_set)
Esempio n. 48
0
    def do_stage(self, images):
        for i, image in enumerate(images):
            try:
                # Set the number of source pixels to be 5% of the total. This keeps us safe from
                # satellites and airplanes.
                sep.set_extract_pixstack(int(image.nx * image.ny * 0.05))

                data = image.data.copy()
                error = (np.abs(data) + image.readnoise ** 2.0) ** 0.5
                mask = image.bpm > 0

                # Fits can be backwards byte order, so fix that if need be and subtract
                # the background
                try:
                    bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
                except ValueError:
                    data = data.byteswap(True).newbyteorder()
                    bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
                bkg.subfrom(data)

                # Do an initial source detection
                # TODO: Add back in masking after we are sure SEP works
                sources = sep.extract(data, self.threshold, minarea=self.min_area,
                                      err=error, deblend_cont=0.005)

                # Convert the detections into a table
                sources = Table(sources)

                # Calculate the ellipticity
                sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

                # Fix any value of theta that are invalid due to floating point rounding
                # -pi / 2 < theta < pi / 2
                sources['theta'][sources['theta'] > (np.pi / 2.0)] -= np.pi
                sources['theta'][sources['theta'] < (-np.pi / 2.0)] += np.pi

                # Calculate the kron radius
                kronrad, krflag = sep.kron_radius(data, sources['x'], sources['y'],
                                                  sources['a'], sources['b'],
                                                  sources['theta'], 6.0)
                sources['flag'] |= krflag
                sources['kronrad'] = kronrad

                # Calcuate the equivilent of flux_auto
                flux, fluxerr, flag = sep.sum_ellipse(data, sources['x'], sources['y'],
                                                      sources['a'], sources['b'],
                                                      np.pi / 2.0, 2.5 * kronrad,
                                                      subpix=1, err=error)
                sources['flux'] = flux
                sources['fluxerr'] = fluxerr
                sources['flag'] |= flag

                # Calculate the FWHMs of the stars:
                fwhm = 2.0 * (np.log(2) * (sources['a'] ** 2.0 + sources['b'] ** 2.0)) ** 0.5
                sources['fwhm'] = fwhm

                # Cut individual bright pixels. Often cosmic rays
                sources = sources[fwhm > 1.0]

                # Measure the flux profile
                flux_radii, flag = sep.flux_radius(data, sources['x'], sources['y'],
                                                   6.0 * sources['a'], [0.25, 0.5, 0.75],
                                                   normflux=sources['flux'], subpix=5)
                sources['flag'] |= flag
                sources['fluxrad25'] = flux_radii[:, 0]
                sources['fluxrad50'] = flux_radii[:, 1]
                sources['fluxrad75'] = flux_radii[:, 2]

                # Calculate the windowed positions
                sig = 2.0 / 2.35 * sources['fluxrad50']
                xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'], sig)
                sources['flag'] |= flag
                sources['xwin'] = xwin
                sources['ywin'] = ywin

                # Calculate the average background at each source
                bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(), sources['x'], sources['y'],
                                                         sources['a'], sources['b'], np.pi / 2.0,
                                                         2.5 * sources['kronrad'], subpix=1)
                #masksum, fluxerr, flag = sep.sum_ellipse(mask, sources['x'], sources['y'],
                #                                         sources['a'], sources['b'], np.pi / 2.0,
                #                                         2.5 * kronrad, subpix=1)

                background_area = (2.5 * sources['kronrad']) ** 2.0 * sources['a'] * sources['b'] * np.pi # - masksum
                sources['background'] = bkgflux
                sources['background'][background_area > 0] /= background_area[background_area > 0]
                # Update the catalog to match fits convention instead of python array convention
                sources['x'] += 1.0
                sources['y'] += 1.0

                sources['xpeak'] += 1
                sources['ypeak'] += 1

                sources['xwin'] += 1.0
                sources['ywin'] += 1.0

                sources['theta'] = np.degrees(sources['theta'])

                image.catalog = sources['x', 'y', 'xwin', 'ywin', 'xpeak', 'ypeak',
                                        'flux', 'fluxerr', 'background', 'fwhm',
                                        'a', 'b', 'theta', 'kronrad', 'ellipticity',
                                        'fluxrad25', 'fluxrad50', 'fluxrad75',
                                        'x2', 'y2', 'xy', 'flag']

                # Add the units and description to the catalogs
                image.catalog['x'].unit = 'pixel'
                image.catalog['x'].description = 'X coordinate of the object'
                image.catalog['y'].unit = 'pixel'
                image.catalog['y'].description = 'Y coordinate of the object'
                image.catalog['xwin'].unit = 'pixel'
                image.catalog['xwin'].description = 'Windowed X coordinate of the object'
                image.catalog['ywin'].unit = 'pixel'
                image.catalog['ywin'].description = 'Windowed Y coordinate of the object'
                image.catalog['xpeak'].unit = 'pixel'
                image.catalog['xpeak'].description = 'X coordinate of the peak'
                image.catalog['ypeak'].unit = 'pixel'
                image.catalog['ypeak'].description = 'Windowed Y coordinate of the peak'
                image.catalog['flux'].unit = 'counts'
                image.catalog['flux'].description = 'Flux within a Kron-like elliptical aperture'
                image.catalog['fluxerr'].unit = 'counts'
                image.catalog['fluxerr'].description = 'Erronr on the flux within a Kron-like elliptical aperture'
                image.catalog['background'].unit = 'counts'
                image.catalog['background'].description = 'Average background value in the aperture'
                image.catalog['fwhm'].unit = 'pixel'
                image.catalog['fwhm'].description = 'FWHM of the object'
                image.catalog['a'].unit = 'pixel'
                image.catalog['a'].description = 'Semi-major axis of the object'
                image.catalog['b'].unit = 'pixel'
                image.catalog['b'].description = 'Semi-minor axis of the object'
                image.catalog['theta'].unit = 'degrees'
                image.catalog['theta'].description = 'Position angle of the object'
                image.catalog['kronrad'].unit = 'pixel'
                image.catalog['kronrad'].description = 'Kron radius used for extraction'
                image.catalog['ellipticity'].description = 'Ellipticity'
                image.catalog['fluxrad25'].unit = 'pixel'
                image.catalog['fluxrad25'].description = 'Radius containing 25% of the flux'
                image.catalog['fluxrad50'].unit = 'pixel'
                image.catalog['fluxrad50'].description = 'Radius containing 50% of the flux'
                image.catalog['fluxrad75'].unit = 'pixel'
                image.catalog['fluxrad75'].description = 'Radius containing 75% of the flux'
                image.catalog['x2'].unit = 'pixel^2'
                image.catalog['x2'].description = 'Variance on X coordinate of the object'
                image.catalog['y2'].unit = 'pixel^2'
                image.catalog['y2'].description = 'Variance on Y coordinate of the object'
                image.catalog['xy'].unit = 'pixel^2'
                image.catalog['xy'].description = 'XY covariance of the object'
                image.catalog['flag'].description = 'Bit mask combination of extraction and photometry flags'

                image.catalog.sort('flux')
                image.catalog.reverse()

                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))

                # Save some background statistics in the header
                mean_background = stats.sigma_clipped_mean(bkg.back(), 5.0)
                image.header['L1MEAN'] = (mean_background,
                                          '[counts] Sigma clipped mean of frame background')
                logs.add_tag(logging_tags, 'L1MEAN', float(mean_background))

                median_background = np.median(bkg.back())
                image.header['L1MEDIAN'] = (median_background,
                                            '[counts] Median of frame background')
                logs.add_tag(logging_tags, 'L1MEDIAN', float(median_background))

                std_background = stats.robust_standard_deviation(bkg.back())
                image.header['L1SIGMA'] = (std_background,
                                           '[counts] Robust std dev of frame background')
                logs.add_tag(logging_tags, 'L1SIGMA', float(std_background))

                # Save some image statistics to the header
                good_objects = image.catalog['flag'] == 0

                seeing = np.median(image.catalog['fwhm'][good_objects]) * image.pixel_scale
                image.header['L1FWHM'] = (seeing, '[arcsec] Frame FWHM in arcsec')
                logs.add_tag(logging_tags, 'L1FWHM', float(seeing))

                mean_ellipticity = stats.sigma_clipped_mean(sources['ellipticity'][good_objects],
                                                            3.0)
                image.header['L1ELLIP'] = (mean_ellipticity, 'Mean image ellipticity (1-B/A)')
                logs.add_tag(logging_tags, 'L1ELLIP', float(mean_ellipticity))

                mean_position_angle = stats.sigma_clipped_mean(sources['theta'][good_objects], 3.0)
                image.header['L1ELLIPA'] = (mean_position_angle,
                                            '[deg] PA of mean image ellipticity')
                logs.add_tag(logging_tags, 'L1ELLIPA', float(mean_position_angle))

                self.logger.info('Extracted sources', extra=logging_tags)

            except Exception as e:
                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
                self.logger.error(e, extra=logging_tags)
        return images
Esempio n. 49
0
 def setup_logging(self, image):
     self.logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
     logs.add_tag(self.logging_tags, 'filename', os.path.basename(image.filename))
Esempio n. 50
0
    def do_stage(self, images):

        for i, image in enumerate(images):
            logging_tags = logs.image_config_to_tags(image,
                                                     self.group_by_keywords)

            # Save the catalog to a temporary file
            filename = os.path.basename(image.filename)
            logs.add_tag(logging_tags, 'filename', filename)

            with tempfile.TemporaryDirectory() as tmpdirname:
                catalog_name = os.path.join(
                    tmpdirname, filename.replace('.fits', '.cat.fits'))
                try:
                    image.write_catalog(catalog_name, nsources=40)
                except image_utils.MissingCatalogException:
                    image.header['WCSERR'] = (
                        4, 'Error status of WCS fit. 0 for no error')
                    self.logger.error(
                        'No source catalog. Not attempting WCS solution',
                        extra=logging_tags)
                    continue

                # Run astrometry.net
                wcs_name = os.path.join(tmpdirname,
                                        filename.replace('.fits', '.wcs.fits'))
                command = self.cmd.format(ra=image.ra,
                                          dec=image.dec,
                                          scale_low=0.9 * image.pixel_scale,
                                          scale_high=1.1 * image.pixel_scale,
                                          wcs_name=wcs_name,
                                          catalog_name=catalog_name,
                                          nx=image.nx,
                                          ny=image.ny)

                console_output = subprocess.check_output(shlex.split(command))
                self.logger.debug(console_output, extra=logging_tags)

                if os.path.exists(wcs_name):
                    # Copy the WCS keywords into original image
                    new_header = fits.getheader(wcs_name)

                    header_keywords_to_update = [
                        'CTYPE1', 'CTYPE2', 'CRPIX1', 'CRPIX2', 'CRVAL1',
                        'CRVAL2', 'CD1_1', 'CD1_2', 'CD2_1', 'CD2_2'
                    ]
                    for keyword in header_keywords_to_update:
                        image.header[keyword] = new_header[keyword]

                    image.header['WCSERR'] = (
                        0, 'Error status of WCS fit. 0 for no error')

                    # Update the RA and Dec header keywords
                    image.header['RA'], image.header[
                        'DEC'] = get_ra_dec_in_sexagesimal(
                            image.header['CRVAL1'], image.header['CRVAL2'])

                    # Clean up wcs file
                    os.remove(wcs_name)

                    # Add the RA and Dec values to the catalog
                    add_ra_dec_to_catalog(image)
                else:
                    image.header['WCSERR'] = (
                        4, 'Error status of WCS fit. 0 for no error')

            logs.add_tag(logging_tags, 'WCSERR', image.header['WCSERR'])
            self.logger.info('Attempted WCS Solve', extra=logging_tags)
        return images
Esempio n. 51
0
 def run_stage(self, image_set, image_config):
     image_set = list(image_set)
     tags = logs.image_config_to_tags(image_set[0], self.group_by_keywords)
     self.logger.info('Running {0}'.format(self.stage_name), extra=tags)
     return self.do_stage(image_set)
Esempio n. 52
0
 def setup_logging(self, image):
     self.logging_tags = logs.image_config_to_tags(image,
                                                   self.group_by_keywords)
     logs.add_tag(self.logging_tags, 'filename',
                  os.path.basename(image.filename))
Esempio n. 53
0
    def do_stage(self, images):
        for i, image in enumerate(images):
            try:
                # Set the number of source pixels to be 5% of the total. This keeps us safe from
                # satellites and airplanes.
                sep.set_extract_pixstack(int(image.nx * image.ny * 0.05))

                data = image.data.copy()
                error = (np.abs(data) + image.readnoise**2.0)**0.5
                mask = image.bpm > 0

                # Fits can be backwards byte order, so fix that if need be and subtract
                # the background
                try:
                    bkg = sep.Background(data,
                                         mask=mask,
                                         bw=32,
                                         bh=32,
                                         fw=3,
                                         fh=3)
                except ValueError:
                    data = data.byteswap(True).newbyteorder()
                    bkg = sep.Background(data,
                                         mask=mask,
                                         bw=32,
                                         bh=32,
                                         fw=3,
                                         fh=3)
                bkg.subfrom(data)

                # Do an initial source detection
                # TODO: Add back in masking after we are sure SEP works
                sources = sep.extract(data,
                                      self.threshold,
                                      minarea=self.min_area,
                                      err=error,
                                      deblend_cont=0.005)

                # Convert the detections into a table
                sources = Table(sources)

                # Calculate the ellipticity
                sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

                # Fix any value of theta that are invalid due to floating point rounding
                # -pi / 2 < theta < pi / 2
                sources['theta'][sources['theta'] > (np.pi / 2.0)] -= np.pi
                sources['theta'][sources['theta'] < (-np.pi / 2.0)] += np.pi

                # Calculate the kron radius
                kronrad, krflag = sep.kron_radius(data, sources['x'],
                                                  sources['y'], sources['a'],
                                                  sources['b'],
                                                  sources['theta'], 6.0)
                sources['flag'] |= krflag
                sources['kronrad'] = kronrad

                # Calcuate the equivilent of flux_auto
                flux, fluxerr, flag = sep.sum_ellipse(data,
                                                      sources['x'],
                                                      sources['y'],
                                                      sources['a'],
                                                      sources['b'],
                                                      np.pi / 2.0,
                                                      2.5 * kronrad,
                                                      subpix=1,
                                                      err=error)
                sources['flux'] = flux
                sources['fluxerr'] = fluxerr
                sources['flag'] |= flag

                # Calculate the FWHMs of the stars:
                fwhm = 2.0 * (np.log(2) *
                              (sources['a']**2.0 + sources['b']**2.0))**0.5
                sources['fwhm'] = fwhm

                # Cut individual bright pixels. Often cosmic rays
                sources = sources[fwhm > 1.0]

                # Measure the flux profile
                flux_radii, flag = sep.flux_radius(data,
                                                   sources['x'],
                                                   sources['y'],
                                                   6.0 * sources['a'],
                                                   [0.25, 0.5, 0.75],
                                                   normflux=sources['flux'],
                                                   subpix=5)
                sources['flag'] |= flag
                sources['fluxrad25'] = flux_radii[:, 0]
                sources['fluxrad50'] = flux_radii[:, 1]
                sources['fluxrad75'] = flux_radii[:, 2]

                # Calculate the windowed positions
                sig = 2.0 / 2.35 * sources['fluxrad50']
                xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'],
                                              sig)
                sources['flag'] |= flag
                sources['xwin'] = xwin
                sources['ywin'] = ywin

                # Calculate the average background at each source
                bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(),
                                                         sources['x'],
                                                         sources['y'],
                                                         sources['a'],
                                                         sources['b'],
                                                         np.pi / 2.0,
                                                         2.5 *
                                                         sources['kronrad'],
                                                         subpix=1)
                #masksum, fluxerr, flag = sep.sum_ellipse(mask, sources['x'], sources['y'],
                #                                         sources['a'], sources['b'], np.pi / 2.0,
                #                                         2.5 * kronrad, subpix=1)

                background_area = (
                    2.5 * sources['kronrad']
                )**2.0 * sources['a'] * sources['b'] * np.pi  # - masksum
                sources['background'] = bkgflux
                sources['background'][background_area > 0] /= background_area[
                    background_area > 0]
                # Update the catalog to match fits convention instead of python array convention
                sources['x'] += 1.0
                sources['y'] += 1.0

                sources['xpeak'] += 1
                sources['ypeak'] += 1

                sources['xwin'] += 1.0
                sources['ywin'] += 1.0

                sources['theta'] = np.degrees(sources['theta'])

                image.catalog = sources['x', 'y', 'xwin', 'ywin', 'xpeak',
                                        'ypeak', 'flux', 'fluxerr',
                                        'background', 'fwhm', 'a', 'b',
                                        'theta', 'kronrad', 'ellipticity',
                                        'fluxrad25', 'fluxrad50', 'fluxrad75',
                                        'x2', 'y2', 'xy', 'flag']

                # Add the units and description to the catalogs
                image.catalog['x'].unit = 'pixel'
                image.catalog['x'].description = 'X coordinate of the object'
                image.catalog['y'].unit = 'pixel'
                image.catalog['y'].description = 'Y coordinate of the object'
                image.catalog['xwin'].unit = 'pixel'
                image.catalog[
                    'xwin'].description = 'Windowed X coordinate of the object'
                image.catalog['ywin'].unit = 'pixel'
                image.catalog[
                    'ywin'].description = 'Windowed Y coordinate of the object'
                image.catalog['xpeak'].unit = 'pixel'
                image.catalog['xpeak'].description = 'X coordinate of the peak'
                image.catalog['ypeak'].unit = 'pixel'
                image.catalog[
                    'ypeak'].description = 'Windowed Y coordinate of the peak'
                image.catalog['flux'].unit = 'counts'
                image.catalog[
                    'flux'].description = 'Flux within a Kron-like elliptical aperture'
                image.catalog['fluxerr'].unit = 'counts'
                image.catalog[
                    'fluxerr'].description = 'Erronr on the flux within a Kron-like elliptical aperture'
                image.catalog['background'].unit = 'counts'
                image.catalog[
                    'background'].description = 'Average background value in the aperture'
                image.catalog['fwhm'].unit = 'pixel'
                image.catalog['fwhm'].description = 'FWHM of the object'
                image.catalog['a'].unit = 'pixel'
                image.catalog[
                    'a'].description = 'Semi-major axis of the object'
                image.catalog['b'].unit = 'pixel'
                image.catalog[
                    'b'].description = 'Semi-minor axis of the object'
                image.catalog['theta'].unit = 'degrees'
                image.catalog[
                    'theta'].description = 'Position angle of the object'
                image.catalog['kronrad'].unit = 'pixel'
                image.catalog[
                    'kronrad'].description = 'Kron radius used for extraction'
                image.catalog['ellipticity'].description = 'Ellipticity'
                image.catalog['fluxrad25'].unit = 'pixel'
                image.catalog[
                    'fluxrad25'].description = 'Radius containing 25% of the flux'
                image.catalog['fluxrad50'].unit = 'pixel'
                image.catalog[
                    'fluxrad50'].description = 'Radius containing 50% of the flux'
                image.catalog['fluxrad75'].unit = 'pixel'
                image.catalog[
                    'fluxrad75'].description = 'Radius containing 75% of the flux'
                image.catalog['x2'].unit = 'pixel^2'
                image.catalog[
                    'x2'].description = 'Variance on X coordinate of the object'
                image.catalog['y2'].unit = 'pixel^2'
                image.catalog[
                    'y2'].description = 'Variance on Y coordinate of the object'
                image.catalog['xy'].unit = 'pixel^2'
                image.catalog['xy'].description = 'XY covariance of the object'
                image.catalog[
                    'flag'].description = 'Bit mask combination of extraction and photometry flags'

                image.catalog.sort('flux')
                image.catalog.reverse()

                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))

                # Save some background statistics in the header
                mean_background = stats.sigma_clipped_mean(bkg.back(), 5.0)
                image.header['L1MEAN'] = (
                    mean_background,
                    '[counts] Sigma clipped mean of frame background')
                logs.add_tag(logging_tags, 'L1MEAN', float(mean_background))

                median_background = np.median(bkg.back())
                image.header['L1MEDIAN'] = (
                    median_background, '[counts] Median of frame background')
                logs.add_tag(logging_tags, 'L1MEDIAN',
                             float(median_background))

                std_background = stats.robust_standard_deviation(bkg.back())
                image.header['L1SIGMA'] = (
                    std_background,
                    '[counts] Robust std dev of frame background')
                logs.add_tag(logging_tags, 'L1SIGMA', float(std_background))

                # Save some image statistics to the header
                good_objects = image.catalog['flag'] == 0

                seeing = np.median(
                    image.catalog['fwhm'][good_objects]) * image.pixel_scale
                image.header['L1FWHM'] = (seeing,
                                          '[arcsec] Frame FWHM in arcsec')
                logs.add_tag(logging_tags, 'L1FWHM', float(seeing))

                mean_ellipticity = stats.sigma_clipped_mean(
                    sources['ellipticity'][good_objects], 3.0)
                image.header['L1ELLIP'] = (mean_ellipticity,
                                           'Mean image ellipticity (1-B/A)')
                logs.add_tag(logging_tags, 'L1ELLIP', float(mean_ellipticity))

                mean_position_angle = stats.sigma_clipped_mean(
                    sources['theta'][good_objects], 3.0)
                image.header['L1ELLIPA'] = (
                    mean_position_angle, '[deg] PA of mean image ellipticity')
                logs.add_tag(logging_tags, 'L1ELLIPA',
                             float(mean_position_angle))

                self.logger.info('Extracted sources', extra=logging_tags)

            except Exception as e:
                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))
                self.logger.error(e, extra=logging_tags)
        return images