def _perform(self):
        """
        Returns an Argument() with the parameters that depends on this operation.
        """
        self.log.info(f"Running {self.__class__.__name__} action")

        header_keywords = ['RN', 'READNOISE']
        for kw in header_keywords:
            rn = self.action.args.kd.get(kw, None)
            if rn is not None: break
        if rn is not None:
            self.log.debug(f'  Got read noise from header: {rn}')

        if rn is None:
            rn = self.cfg['Telescope'].getfloat('RN', None)
            self.log.debug(f'  Got read noise from config: {rn}')
            self.action.args.kd.headers.append(fits.Header({'READNOISE': rn}))

        for i, pd in enumerate(self.action.args.kd.pixeldata):
            if pd.unit == u.electron:
                self.action.args.kd.pixeldata[i] = ccdproc.create_deviation(
                    pd, readnoise=rn * u.electron)
            elif pd.unit == u.adu:
                self.action.args.kd.pixeldata[i] = ccdproc.create_deviation(
                    pd,
                    gain=float(self.action.args.kd.get('GAIN')),
                    readnoise=rn * u.electron)
            else:
                self.log.error('Could not estimate uncertainty')

        return self.action.args
Ejemplo n.º 2
0
def test_log_bad_type_fails():
    ccd_data = ccd_data_func()
    add_key = 15  # anything not string and not dict-like will work here
    # Do we fail with non-string, non-Keyword, non-dict-like value?
    with pytest.raises(AttributeError):
        create_deviation(ccd_data,
                         readnoise=3 * ccd_data.unit,
                         add_keyword=add_key)
Ejemplo n.º 3
0
def test_log_keyword():
    ccd_data = ccd_data_func()
    key = 'filter'
    key_val = 'V'
    kwd = Keyword(key, value=key_val)
    new = create_deviation(ccd_data,
                           readnoise=3 * ccd_data.unit,
                           add_keyword=kwd)
    # Was the Keyword added with the correct value?
    assert kwd.name in new.meta
    assert kwd.name not in ccd_data.meta
    assert new.meta[kwd.name] == key_val
Ejemplo n.º 4
0
def test_log_string(key):
    ccd_data = ccd_data_func()
    add_key = key
    new = create_deviation(ccd_data,
                           readnoise=3 * ccd_data.unit,
                           add_keyword=add_key)
    # Keys should be added to new but not to ccd_data and should have
    # no value.
    assert add_key in new.meta
    assert add_key not in ccd_data.meta
    # Long keyword names should be accessible with just the keyword name
    # without HIERARCH -- is it?
    assert new.meta[add_key] is None
Ejemplo n.º 5
0
def test_log_dict():
    ccd_data = ccd_data_func()
    keys_to_add = {
        'process': 'Added deviation',
        'n_images_input': 1,
        'current_temp': 42.9
    }
    new = create_deviation(ccd_data,
                           readnoise=3 * ccd_data.unit,
                           add_keyword=keys_to_add)
    for k, v in keys_to_add.items():
        # Were all dictionary items added?
        assert k in new.meta
        assert k not in ccd_data.meta
        assert new.meta[k] == v
Ejemplo n.º 6
0
def test_implicit_logging():
    ccd_data = ccd_data_func()
    # If nothing is supplied for the add_keyword argument then the following
    # should happen:
    # + A key named func.__name__ is created, with
    # + value that is the list of arguments the function was called with.
    bias = CCDData(np.zeros_like(ccd_data.data), unit="adu")
    result = subtract_bias(ccd_data, bias)
    assert "subtract_bias" in result.header
    assert result.header['subtract_bias'] == (
        'subbias', 'Shortened name for ccdproc command')
    assert result.header['subbias'] == "ccd=<CCDData>, master=<CCDData>"

    result = create_deviation(ccd_data, readnoise=3 * ccd_data.unit)
    assert result.header['create_deviation'] == (
        'creatvar', 'Shortened name for ccdproc command')
    assert ("readnoise=" + str(3 * ccd_data.unit) in result.header['creatvar'])
Ejemplo n.º 7
0
    def _perform(self):
        """
        Returns an Argument() with the parameters that depend on this
        operation.
        """
        self.log.info(f"Running {self.__class__.__name__} action")

        read_noise = self.action.args.meta.get('read_noise', None)
        if read_noise is not None:
            self.log.debug(f'  Using read_noise = {read_noise}')
        if read_noise is None:
            read_noise = self.cfg['Telescope'].getfloat('read_noise', None)
            self.log.debug(f'  Got read_noise from config: {read_noise}')

        self.action.args.ccddata = ccdproc.create_deviation(
            self.action.args.ccddata, readnoise=read_noise * u.electron)

        return self.action.args
Ejemplo n.º 8
0
 def reduceFrames(self):
     self.getMedianForObjects()
     log.info('Reducing frames started')
     print 'Reducing frames started'
     
     for obj in self.objects.filesList:
         data = ccdproc.CCDData.read(obj, unit=u.adu)
         dataWithDeviation = ccdproc.create_deviation(data, 
                                                     gain=1.5*u.electron/u.adu,
                                                     readnoise=5*u.electron)
         reducedObject = ccdproc.gain_correct(dataWithDeviation, 
                                             1.5*u.electron/u.adu)
         
         if self.biases.isExists:
             reducedObject = ccdproc.subtract_bias(reducedObject, 
                                                   self.biases.masterCCD)
         if self.darks.isExists:
             reducedObject = ccdproc.subtract_dark(reducedObject, 
                                                   self.darks.masterCCD, 
                                                   exposure_time=cfg.exptime, 
                                                   exposure_unit=u.second, 
                                                   scale=True)
         if self.flats.isExists:
             reducedObject = ccdproc.flat_correct(reducedObject, 
                                                  self.flats.masterCCD)
         
         self.directory = '../../Reduction/' + directoryName
         if not os.path.exists(self.directory):
             os.makedirs(self.directory)
         
         reducedObject.write(self.directory + '/' + obj, clobber=True)
         os.system('solve-field ' + self.directory + '/' + obj) # + ' --overwrite')
         
         objName, objExtension = os.path.splitext(self.directory + '/' + obj)
         
         if not os.path.exists(objName + '.new'):
             log.warning(objName + ' cannot be solved')
         else:
             newObjectsList.append(objName + '.new')
             log.info('Frame ' + objName + ' reduced')
         
     log.info('Reduced ' + str(len(newObjectsList)) + ' frames')
     print 'Reduced ' + str(len(newObjectsList)) + ' frames'
     self.clean()
Ejemplo n.º 9
0
def convert_to_ccddata(images, gain=None, read_noise=None):
    """
    Convert 'fits' file to 'ccdproc.CCCData' object.

    Parameters
    ----------
    images : str or list of str.
        Images to converted.

    gain : float
        Gain (u.electron / u.adu)

    read_noise : float
        Read Noise (u.electron).

    Yields
    ------
    'ccdproc.CCDData'
        yield the next 'ccdproc.CCDData'.

    Examples
    --------
    >>> from tuglib.io import convert_to_ccddata
    >>> from glob import glob
    >>>
    >>> images = glob('/home/user/data/image*.fits')
    >>>
    >>> ccds = convert_to_ccddata(images, gain=0.37, read_noise=4.11)
    """

    if not isinstance(images, (str, list)):
        raise TypeError("'images' should be 'str' or 'list' object.")

    if not isinstance(gain, (type(None), float)):
        raise TypeError("'gain' should be 'None' or 'float' object.")

    if not isinstance(read_noise, (type(None), float)):
        raise TypeError("'read_noise' should be 'None' or 'float' object.")

    if isinstance(images, str):
        images = [images]

    unit = u.adu

    if gain is not None:
        gain = gain * u.electron / u.adu

    if read_noise is not None:
        read_noise = read_noise * u.electron

    for image in images:
        ccd = CCDData.read(image, unit=unit, output_verify='silentfix+ignore')

        if (gain is not None) and (read_noise is not None):
            data_with_deviation = create_deviation(ccd,
                                                   gain=gain,
                                                   readnoise=read_noise)

            gain_corrected = gain_correct(data_with_deviation, gain)

            yield gain_corrected
        else:
            yield ccd
Ejemplo n.º 10
0
def test_log_set_to_None_does_not_change_header():
    ccd_data = ccd_data_func()
    new = create_deviation(ccd_data,
                           readnoise=3 * ccd_data.unit,
                           add_keyword=None)
    assert new.meta.keys() == ccd_data.header.keys()
Ejemplo n.º 11
0
    def process_raw_frame(self, master_bias, master_flat, pixel_mask_spec=None):
        """
        Bias and flat-correct a raw CCD frame. Trim off the overscan
        region. Identify cosmic rays using "lacosmic" and inflat
        uncertainties where CR's are found. If specified, mask out
        nearby sources by setting pixel uncertainty to infinity (or
        inverse-variance to 0).

        Returns
        -------
        nccd : `ccdproc.CCDData`
            A copy of the original ``CCDData`` object but after the
            above procedures have been run.
        """

        oscan_fits_section = "[{}:{},:]".format(self.oscan_idx,
                                                self.oscan_idx+self.oscan_size)

        # make a copy of the object
        nccd = self.ccd.copy()

        # apply the overscan correction
        poly_model = Polynomial1D(2)
        nccd = ccdproc.subtract_overscan(nccd, fits_section=oscan_fits_section,
                                         model=poly_model)

        # trim the image (remove overscan region)
        nccd = ccdproc.trim_image(nccd, fits_section='[1:{},:]'.format(self.oscan_idx))

        # create the error frame
        nccd = ccdproc.create_deviation(nccd, gain=self.ccd_gain,
                                        readnoise=self.ccd_readnoise)

        # now correct for the ccd gain
        nccd = ccdproc.gain_correct(nccd, gain=self.ccd_gain)

        # correct for master bias frame
        # - this does some crazy shit at the blue end, but we can live with it
        nccd = ccdproc.subtract_bias(nccd, master_bias)

        # correct for master flat frame
        nccd = ccdproc.flat_correct(nccd, master_flat)

        # comsic ray cleaning - this updates the uncertainty array as well
        nccd = ccdproc.cosmicray_lacosmic(nccd, sigclip=8.)

        # replace ccd with processed ccd
        self.ccd = nccd

        # check for a pixel mask
        if pixel_mask_spec is not None:
            mask = self.make_nearby_source_mask(pixel_mask_spec)
            logger.debug("\t\tSource mask loaded.")

            stddev = nccd.uncertainty.array
            stddev[mask] = np.inf
            nccd.uncertainty = StdDevUncertainty(stddev)

        if self.plot_path is not None:
            # TODO: this assumes vertical CCD
            aspect_ratio = nccd.shape[1]/nccd.shape[0]

            fig,axes = plt.subplots(2, 1, figsize=(10,2 * 12*aspect_ratio),
                                    sharex=True, sharey=True)

            vmin,vmax = self.zscaler.get_limits(nccd.data)
            axes[0].imshow(nccd.data.T, origin='bottom',
                           cmap=self.cmap, vmin=max(0,vmin), vmax=vmax)

            stddev = nccd.uncertainty.array
            vmin,vmax = self.zscaler.get_limits(stddev[np.isfinite(stddev)])
            axes[1].imshow(stddev.T, origin='bottom',
                           cmap=self.cmap, vmin=max(0,vmin), vmax=vmax)

            axes[0].set_title('Object: {0}, flux'.format(self._obj_name))
            axes[1].set_title('root-variance'.format(self._obj_name))

            fig.tight_layout()
            fig.savefig(path.join(self.plot_path, '{}_frame.png'.format(self._filename_base)))
            plt.close(fig)

        return nccd
Ejemplo n.º 12
0
def calibrate_images(x_d, x_f, x_s, it_s = 'object', x_b = '', ):
	"""
	Parameters
	----------
	----------
	x_b : str
		Path of the bias files.
		By default bias is not provided
	x_d : str
		Path of the dark files
	x_f : str
		Path of flat files
	x_s : str
		Path of Science files
	it_s : str
		Imagetyp of fits science file
		Default is set to object
	-----------
	"""
	path_b = Path(x_b)
	path_d = Path(x_d)
	path_f = Path(x_f)
	path_s = Path(x_s)
	
	#-----Bias
	if x_b == '' and path_b == Path(''):
		print('Be aware: You did not provide the Bias files; the process will still continue though.')
		files_b = None
	elif not path_b.is_dir():
		raise RuntimeError('The path you provided for the Bias files does not exist.')
	else:
		files_b = ccdp.ImageFileCollection(path_b)
	#-----Dark
	if x_d == '' or not path_d.is_dir():
		raise RuntimeError('You must provide Dark files for processing.\n Or the path you provided does not exist.')
	else:
		files_d = ccdp.ImageFileCollection(path_d)
	#-----Flat
	if x_f == '' or not path_f.is_dir():
		raise RuntimeError('You must provide Flatfield files for processing.\n Or the path you provided does not exist.')
	else:
		files_f = ccdp.ImageFileCollection(path_f)
	#-----Science
	if x_s == '' or not path_s.is_dir():
		raise RuntimeError('You must provide Science images for processing.\n Or the path you provided does not exist.')
	else:
		files_s = ccdp.ImageFileCollection(path_s)
	
	#-----------------------------------
	#
	#--------Calibrating Images---------
	#
	#-----------------------------------
	
	if files_b is not None:
		#-------------------------------
		#------Creating Master-bias-----
		#-------------------------------
		cali_bias_path = Path(path_b / 'cali_bias')
		cali_bias_path.mkdir(exist_ok = True)
		files_b_cali = files_b.files_filtered(imagetyp = 'bias', include_path = True)
		combined_bias = ccdp.combine(files_b_cali,\
						method='average',\
						sigma_clip=True,\
						sigma_clip_low_thresh=5,\
						sigma_clip_high_thresh=5,\
						sigma_clip_func=np.ma.median,\
						sigma_clip_dev_func=mad_std,\
						mem_limit=350e6)
		combined_bias.meta['combined'] = True
		combined_bias.write(cali_bias_path / 'master_bias.fits')
		# Reading master bias
		master_bias = CCDData.read(cali_bias_path / 'master_bias.fits')
	else:
		master_bias = None
	
	#-------------------------------
	#-------Calibrating Darks-------
	#-------------------------------
	cali_dark_path = Path(path_d / 'cali_dark')
	cali_dark_path.mkdir(exist_ok = True)
	files_d_cali = files_d.files_filtered(imagetyp = 'DARK', include_path = True)
	for ccd, file_name in files_d.ccds(imagetyp = 'DARK', return_fname = True, ccd_kwargs = {'unit':'adu'}):
		if master_bias is not None:
			# Subtract bias
			ccd = ccdp.subtract_bias(ccd, master_bias)
		else:
			ccd = ccd
		# Save the result 
		ccd.write(cali_dark_path / file_name)
	
	#--------------------------------
	#------Creating Master-Dark------
	#--------------------------------
	red_dark = ccdp.ImageFileCollection(cali_dark_path)
	# Calculating exposure times of DARK images
	dark_times = set(red_dark.summary['exptime'][red_dark.summary['imagetyp'] == 'DARK'])
	for exposure in sorted(dark_times):
		cali_darks = red_dark.files_filtered(imagetyp = 'dark',\
							exptime = exposure,\
							include_path = True)
		combined_dark = ccdp.combine(cali_darks,\
						method='average',\
						sigma_clip=True,\
						sigma_clip_low_thresh=5,\
						sigma_clip_high_thresh=5,\
						sigma_clip_func=np.ma.median,\
						sigma_clip_dev_func=mad_std,\
						mem_limit=350e6)
		combined_dark.meta['combined'] = True
		com_dark_name = 'combined_dark_{:6.3f}.fits'.format(exposure)
		combined_dark.write(cali_dark_path / com_dark_name)
	# Reading master dark of various exposure times
	red_dark = ccdp.ImageFileCollection(cali_dark_path)
	combined_darks = {ccd.header['exptime']: ccd for ccd in red_dark.ccds(imagetyp = 'DARK', combined = True)}
	
	#--------------------------------
	#-------Calibrating Flats--------
	#--------------------------------
	cali_flat_path = Path(path_f / 'cali_flat')
	cali_flat_path.mkdir(exist_ok = True)
	files_f_cali = files_f.files_filtered(imagetyp = 'FLAT', include_path = True)
	for ccd, file_name in files_f.ccds(imagetyp = 'FLAT', ccd_kwargs = {'unit' : 'adu'}, return_fname = True):
		# Subtract bias
		if master_bias is not None:
			ccd = ccdp.subtract_bias(ccd, master_bias)
		else:
			ccd = ccd
		closest_dark = utl.find_nearest_dark_exposure(ccd, dark_times)
		if closest_dark is None:
			closest_dark1 = utl.find_nearest_dark_exposure(ccd, dark_times, tolerance = 100)
			# Subtract scaled Dark
			ccd = ccdp.subtract_dark(ccd, combined_darks[closest_dark1],\
							exposure_time = 'exptime',\
							exposure_unit = u.second,\
							scale = True)
			ccd.write(cali_flat_path / ('flat-' + file_name))
		else:
			closest_dark2 = utl.find_nearest_dark_exposure(ccd, dark_times)
			# Subtracting Darks
			ccd = ccdp.subtract_dark(ccd, combined_darks[closest_dark2], exposure_time = 'exptime', exposure_unit = u.second)
			ccd.write(cali_flat_path / ('flat-' + file_name))
	
	#--------------------------------
	#-----Creating Master-Flat-------
	#--------------------------------
	red_flats = ccdp.ImageFileCollection(cali_flat_path)
	cali_flats = red_flats.files_filtered(imagetyp = 'FLAT', include_path = True)
	combined_flat = ccdp.combine(cali_flats,\
					method='average',\
					scale = utl.inverse_median,\
					sigma_clip=True,\
					sigma_clip_low_thresh=5,\
					sigma_clip_high_thresh=5,\
					sigma_clip_func=np.ma.median,\
					sigma_clip_dev_func=mad_std,\
					mem_limit=350e6)
	combined_flat.meta['combined'] = True
	combined_flat.write(cali_flat_path / 'master_flat.fits')
	# Reading master flat
	red_flats = ccdp.ImageFileCollection(cali_flat_path)
	combined_flat = CCDData.read(cali_flat_path / 'master_flat.fits')
	
	#--------------------------------
	#---Calibrating Science Images---
	#--------------------------------
	cali_science_path = Path(path_s / 'cali_science')	
	cali_science_path.mkdir(exist_ok = True)	
	
	# Correcting for flat	
	for ccd, file_name in files_s.ccds(imagetyp = it_s, ccd_kwargs = {'unit' : 'adu'}, return_fname = True):	
		# Subtract scaled Dark	
		#ccd = ccdp.subtract_dark(ccd, combined_darks[closest_dark1], exposure_time = 'exptime', exposure_unit = u.second, scale = True)	
		ccd = ccdp.flat_correct(ccd, combined_flat)#['FLAT'])	
		ccd.write(cali_science_path / file_name)	

	files_s1 = ccdp.ImageFileCollection(cali_science_path)	
	files_s_cali = files_s1.files_filtered(imagetyp = it_s, include_path = True)
	
	# Creating a list of spectrum images
	files_spec = files_s1.summary['file', 'view_pos']
	files_spec_list = np.array([])
	for i in range(len(files_spec)):
		xxx = files_spec['view_pos'][i]
		if xxx[0:4] == 'open':
			files_spec_list = np.hstack((files_spec_list, files_spec['file'][i]))
	
	
	# Sky subtracting images
	final_calibrated = Path(path_s / 'Final_calibrated_science')	
	final_calibrated.mkdir(exist_ok = True)
	
	# Variance in sky subtracting images
	final_calibrated_err = Path(path_s / 'Error_final_calibrated_science')	
	final_calibrated_err.mkdir(exist_ok = True)

	j = 0
	for i in range(int(len(files_spec_list)/2)):
		# For Reduced Image
		ccd1 = CCDData.read(x_s + 'cali_science/' + files_spec_list[j], unit='adu')
		ccd2 = CCDData.read(x_s + 'cali_science/' + files_spec_list[j+1], unit = 'adu')
		sky_sub1 = ccd1.data - ccd2.data
		ss1 = CCDData(sky_sub1, unit='adu')
		ss1.header = ccd1.header
		ss1.meta['sky_sub'] = True
		name1 = 'sky_sub_' + files_spec_list[j]
		ss1.write(final_calibrated / name1)
		sky_sub2 = ccd2.data - ccd1.data
		ss2 = CCDData(sky_sub2, unit='adu')
		ss2.header = ccd2.header
		ss2.meta['sky_sub'] = True
		name2 = 'sky_sub_' + files_spec_list[j+1]
		ss2.write(final_calibrated / name2)
		# For Errors in Reduced Image
		ccd1e = ccdp.create_deviation(ccd1, gain=9.2*u.electron/u.adu, readnoise=40*u.electron)
		ccd1er = ccd1e.uncertainty.array
		ccd1err = np.nan_to_num(ccd1er)

		ccd2e = ccdp.create_deviation(ccd2, gain=9.2*u.electron/u.adu, readnoise=40*u.electron)
		ccd2er = ccd2e.uncertainty.array
		ccd2err = np.nan_to_num(ccd2er)

		data_err = np.sqrt(ccd1err**2 + ccd2err**2 - (2*ccd1err*ccd2err))
		data_err1 = CCDData(data_err, unit='adu')
		data_err1.meta['Error'] = True
		data_err1.header = ccd1.header
		
		name3 = 'sky_sub_err_' + files_spec_list[j]
		data_err1.write(final_calibrated_err / name3)
		name4 = 'sky_sub_err_' + files_spec_list[j+1]
		data_err1.write(final_calibrated_err / name4)
		j = j+2
Ejemplo n.º 13
0
def ccd_process(ccd, oscan=None, trim=None, error=False, masterbias=None,
                bad_pixel_mask=None, gain=None, rdnoise=None,
                oscan_median=True, oscan_model=None):
    """Perform basic processing on ccd data.

       The following steps can be included:
        * overscan correction
        * trimming of the image
        * create edeviation frame
        * gain correction
        * add a mask to the data
        * subtraction of master bias

       The task returns a processed `ccdproc.CCDData` object.

    Parameters
    ----------
    ccd: `ccdproc.CCDData`
        Frame to be reduced

    oscan: None, str, or, `~ccdproc.ccddata.CCDData`
        For no overscan correction, set to None.   Otherwise proivde a region
        of `ccd` from which the overscan is extracted, using the FITS
        conventions for index order and index start, or a
        slice from `ccd` that contains the overscan.

    trim: None or str
        For no trim correction, set to None.   Otherwise proivde a region
        of `ccd` from which the image should be trimmed, using the FITS
        conventions for index order and index start.

    error: boolean
        If True, create an uncertainty array for ccd

    masterbias: None, `~numpy.ndarray`,  or `~ccdproc.CCDData`
        A materbias frame to be subtracted from ccd.

    bad_pixel_mask: None or `~numpy.ndarray`
        A bad pixel mask for the data. The bad pixel mask should be in given
        such that bad pixels havea value of 1 and good pixels a value of 0.

    gain: None or `~astropy.Quantity`
        Gain value to multiple the image by to convert to electrons

    rdnoise: None or `~astropy.Quantity`
        Read noise for the observations.  The read noise should be in
        `~astropy.units.electron`


    oscan_median :  bool, optional
        If true, takes the median of each line.  Otherwise, uses the mean

    oscan_model :  `~astropy.modeling.Model`, optional
        Model to fit to the data.  If None, returns the values calculated
        by the median or the mean.

    Returns
    -------
    ccd: `ccdproc.CCDData`
        Reduded ccd

    Examples
    --------

    1. To overscan, trim, and gain correct a data set:

    >>> import numpy as np
    >>> from astropy import units as u
    >>> from hrsprocess import ccd_process
    >>> ccd = CCDData(np.ones([100, 100]), unit=u.adu)
    >>> nccd = ccd_process(ccd, oscan='[1:10,1:100]', trim='[10:100, 1,100]',
                           error=False, gain=2.0*u.electron/u.adu)


    """
    # make a copy of the object
    nccd = ccd.copy()

    # apply the overscan correction
    if isinstance(oscan, ccdproc.CCDData):
        nccd = ccdproc.subtract_overscan(nccd, overscan=oscan,
                                         median=oscan_median,
                                         model=oscan_model)
    elif isinstance(oscan, six.string_types):
        nccd = ccdproc.subtract_overscan(nccd, fits_section=oscan,
                                         median=oscan_median,
                                         model=oscan_model)
    elif oscan is None:
        pass
    else:
        raise TypeError('oscan is not None, a string, or CCDData object')

    # apply the trim correction
    if isinstance(trim, six.string_types):
        nccd = ccdproc.trim_image(nccd, fits_section=trim)
    elif trim is None:
        pass
    else:
        raise TypeError('trim is not None or a string')

    # create the error frame
    if error and gain is not None and rdnoise is not None:
        nccd = ccdproc.create_deviation(nccd, gain=gain, rdnoise=rdnoise)
    elif error and (gain is None or rdnoise is None):
        raise ValueError(
            'gain and rdnoise must be specified to create error frame')

    # apply the bad pixel mask
    if isinstance(bad_pixel_mask, np.ndarray):
        nccd.mask = bad_pixel_mask
    elif bad_pixel_mask is None:
        pass
    else:
        raise TypeError('bad_pixel_mask is not None or numpy.ndarray')

    # apply the gain correction
    if isinstance(gain, u.quantity.Quantity):
        nccd = ccdproc.gain_correct(nccd, gain)
    elif gain is None:
        pass
    else:
        raise TypeError('gain is not None or astropy.Quantity')

    # test subtracting the master bias
    if isinstance(masterbias, ccdproc.CCDData):
        nccd = ccdproc.subtract_bias(nccd, masterbias)
    elif isinstance(masterbias, np.ndarray):
        nccd.data = nccd.data - masterbias
    elif masterbias is None:
        pass
    else:
        raise TypeError(
            'masterbias is not None, numpy.ndarray,  or a CCDData object')

    return nccd
Ejemplo n.º 14
0
def process_fits(fitspath,
                 *,
                 obstype=None,
                 object=None,
                 exposure_times=None,
                 percentile=None,
                 percentile_min=None,
                 percentile_max=None,
                 window=None,
                 darks=None,
                 cosmic_ray=False,
                 cosmic_ray_kwargs={},
                 gain=None,
                 readnoise=None,
                 normalise=False,
                 normalise_func=np.ma.average,
                 combine_type=None,
                 sigma_clip=False,
                 low_thresh=3,
                 high_thresh=3):
    """Combine all FITS images of a given type and exposure time from a given directory.

    Parameters
    ----------
    fitspath: str
        Path to the FITS images to process. Can be a path to a single file, or a path to a
        directory. If the latter the directory will be searched for FITS files and checked
        against criteria from obstype, object, exposure_times critera.
    obstype: str, optional
        Observation type, an 'OBSTYPE' FITS header value e.g. 'DARK', 'OBJ'. If given only files
        with matching OBSTYPE will be processed.
    object: str, optional
        Object name, i.e. 'OBJECT' FITS header value. If given only files with matching OBJECT
        will be processed.
    exposure_times: float or sequence, optional
        Exposure time(s), i.e 'TOTALEXP' FITS header value(s). If given only files with matching
        TOTALEXP will be processed.
    percentile: float, optional
        If given will only images whose percentile value fall between percentile_min and
        percentile_max will be processed, e.g. set to 50.0 to select images by median value,
        set to 99.5 to select images by their 99.5th percentile value.
    percentile_min: float, optional
        Minimum percentile value.
    percentile_max: float, optional
        Maximum percentil value.
    window: (int, int, int, int), optional
        If given will trim images to the window defined as (x0, y0, x1, y1), where (x0, y0)
        and (x1, y1) are the coordinates of the bottom left and top right corners.
    darks: str or sequence, optional
        Filename(s) of dark frame(s) to subtract from the image(s). If given a dark frame with
        matching TOTALEXP will be subtracted from each image during processing.
    cosmic_ray: bool, optional
        Whether to perform single image cosmic ray removal, using the lacosmic algorithm,
        default False. Requires both gain and readnoise to be set.
    cosmic_ray_kwargs: dict, optional
        Additional keyword arguments to pass to the ccdproc.cosmicray_lacosmic function.
    gain: str or astropy.units.Quantity, optional
        Either a string indicating the FITS keyword corresponding to the (inverse gain), or
        a Quantity containing the gain value to use. If both gain and read noise are given
        an uncertainty frame will be created.
    readnoise: str or astropy.units.Quantity, optional
        Either a string indicating the FITS keyword corresponding to read noise, or a Quantity
        containing the read noise value to use. If both read noise and gain are given then an
        uncertainty frame will be created.
    normalise: bool, optional
        If True each image will be normalised. Default False.
    normalise_func: callable, optional
        Function to use for normalisation. Each image will be divided by normalise_func(image).
        Default np.ma.average.
    combine_type: str, optional
        Type of image combination to use, 'MEAN' or 'MEDIAN'. If None the individual
        images will be processed but not combined and the return value will be a list of
        CCDData objects. Default None.
    sigma_clip: bool, optional
        If True will perform sigma clipping on the image stack before combining, default=False.
    low_thresh: float, optional
        Lower threshold to use for sigma clipping, in standard deviations. Default is 3.0.
    high_thresh: float, optional
        Upper threshold to use for sigma clipping, in standard deviations. Default is 3.0.


    Returns
    -------
    master: ccdproc.CCDData
        Combined image.

    """
    if exposure_times:
        try:
            # Should work for any sequence or iterable type
            exposure_times = set(exposure_times)
        except TypeError:
            # Not a sequence or iterable, try using as a single value.
            exposure_times = {
                float(exposure_times),
            }

    if darks:
        try:
            dark_filenames = set(darks)
        except TypeError:
            dark_filenames = {
                darks,
            }
        dark_dict = {}
        for filename in dark_filenames:
            try:
                dark_data = CCDData.read(filename)
            except ValueError:
                # Might be no units in FITS header. Assume ADU.
                dark_data = CCDData.read(filename, unit='adu')
            dark_dict[dark_data.header['totalexp']] = dark_data

    if combine_type and combine_type not in ('MEAN', 'MEDIAN'):
        raise ValueError(
            "combine_type must be 'MEAN' or 'MEDIAN', got '{}''".format(
                combine_type))

    fitspath = Path(fitspath)
    if fitspath.is_file():
        # FITS path points to a single file, turn into a list.
        filenames = [
            fitspath,
        ]
    elif fitspath.is_dir():
        # FITS path is a directory. Find FITS file and collect values of selected FITS headers
        ifc = ImageFileCollection(fitspath, keywords='*')
        if len(ifc.files) == 0:
            raise RuntimeError("No FITS files found in {}".format(fitspath))
        # Filter by observation type.
        if obstype:
            try:
                ifc = ifc.filter(obstype=obstype)
            except FileNotFoundError:
                raise RuntimeError(
                    "No FITS files with OBSTYPE={}.".format(obstype))
        # Filter by object name.
        if object:
            try:
                ifc = ifc.filter(object=object)
            except FileNotFoundError:
                raise RuntimeError(
                    "No FITS files with OBJECT={}.".format(object))
        filenames = [
            Path(ifc.location).joinpath(filename) for filename in ifc.files
        ]
    else:
        raise ValueError(
            "fitspath '{}' is not an accessible file or directory.".format(
                fitspath))

    # Load image(s) and process them.
    images = []
    for filename in filenames:
        try:
            ccddata = CCDData.read(filename)
        except ValueError:
            # Might be no units in FITS header. Assume ADU.
            ccddata = CCDData.read(filename, unit='adu')
        # Filtering by exposure times here because it's hard filter ImageFileCollection
        # with an indeterminate number of possible values.
        if not exposure_times or ccddata.header['totalexp'] in exposure_times:
            if window:
                ccddata = ccdproc.trim_image(ccddata[window[1]:window[3] + 1,
                                                     window[0]:window[2] + 1])

            if percentile:
                # Check percentile value is within specified range, otherwise skip to next image.
                percentile_value = np.percentile(ccddata.data, percentile)
                if percentile_value < percentile_min or percentile_value > percentile_max:
                    continue

            if darks:
                try:
                    ccddata = ccdproc.subtract_dark(
                        ccddata,
                        dark_dict[ccddata.header['totalexp']],
                        exposure_time='totalexp',
                        exposure_unit=u.second)
                except KeyError:
                    raise RuntimeError(
                        "No dark with matching totalexp for {}.".format(
                            filename))

            if gain:
                if isinstance(gain, str):
                    egain = ccddata.header[gain]
                    egain = egain * u.electron / u.adu
                elif isinstance(gain, u.Quantity):
                    try:
                        egain = gain.to(u.electron / u.adu)
                    except u.UnitsError:
                        egain = (1 / gain).to(u.electron / u.adu)
                else:
                    raise ValueError(
                        f"gain must be a string or Quantity, got {gain}.")

            if readnoise:
                if isinstance(readnoise, str):
                    rn = ccddata.header[readnoise]
                    rn = rn * u.electron
                elif isinstance(readnoise, u.Quantity):
                    try:
                        rn = readnoise.to(u.electron / u.pixel)
                    except u.UnitsError:
                        rn = (readnoise * u.pixel).to(u.electron)
                else:
                    raise ValueError(
                        f"readnoise must be a string or Quantity, got {readnoise}."
                    )

            if gain and readnoise:
                ccddata = ccdproc.create_deviation(ccddata,
                                                   gain=egain,
                                                   readnoise=rn,
                                                   disregard_nan=True)

            if gain:
                ccddata = ccdproc.gain_correct(ccddata, gain=egain)

            if cosmic_ray:
                if not gain and readnoise:
                    raise ValueError(
                        "Cosmic ray removal required both gain & readnoise.")

                ccddata = ccdproc.cosmicray_lacosmic(
                    ccddata,
                    gain=1.0,  # ccddata already gain corrected
                    readnoise=rn,
                    **cosmic_ray_kwargs)

            if normalise:
                ccddata = ccddata.divide(normalise_func(ccddata.data))

            images.append(ccddata)

    n_images = len(images)
    if n_images == 0:
        msg = "No FITS files match exposure time criteria"
        raise RuntimeError(msg)

    if n_images == 1 and combine_type:
        warn(
            "Combine type '{}' selected but only 1 matching image, skipping image combination.'"
        )
        combine_type = None

    if combine_type:
        combiner = Combiner(images)

        # Sigma clip data
        if sigma_clip:
            if combine_type == 'MEAN':
                central_func = np.ma.average
            else:
                # If not MEAN has to be MEDIAN, checked earlier that it was one or the other.
                central_func = np.ma.median
            combiner.sigma_clipping(low_thresh=low_thresh,
                                    high_thresh=high_thresh,
                                    func=central_func)

        # Stack images.
        if combine_type == 'MEAN':
            master = combiner.average_combine()
        else:
            master = combiner.median_combine()

        # Populate header of combined image with metadata about the processing.
        master.header['fitspath'] = str(fitspath)
        if obstype:
            master.header['obstype'] = obstype
        if exposure_times:
            if len(exposure_times) == 1:
                master.header['totalexp'] = float(exposure_times.pop())
            else:
                master.header['totalexp'] = tuple(exposure_times)
        master.header['nimages'] = n_images
        master.header['combtype'] = combine_type
        master.header['sigclip'] = sigma_clip
        if sigma_clip:
            master.header['lowclip'] = low_thresh
            master.header['highclip'] = high_thresh

    else:
        # No image combination, just processing indivudal image(s)
        if n_images == 1:
            master = images[0]
        else:
            master = images

    return master
Ejemplo n.º 15
0
    def ccds(self, masks=None, trim=None, **kwargs):
        """
        Generator that yields each 'ccdproc.CCDData' objects in the collection.

        Parameters
        ----------
        masks : str, list of str or optional
            Area to be masked.

        trim : str or optional
            Trim section.

        **kwargs :
            Any additional keywords are used to filter the items returned.

        Yields
        ------
        'ccdproc.CCDData'
            yield the next 'ccdproc.CCDData' in the collection.

        Examples
        --------
        >>> from tuglib.io import FitsCollection
        >>>
        >>> mask = '[:, 1000:1046]'
        >>> trim = '[100:1988, :]'
        >>> images = FitsCollection(
                location='/home/user/data/fits/', gain=0.57, read_noise=4.11)
        >>> biases = images.ccds(OBJECT='BIAS', masks=mask, trim=trim)
        """

        if masks is not None:
            if not isinstance(masks, (str, list, type(None))):
                raise TypeError(
                    "'masks' should be 'str', 'list' or 'None' object.")

        if trim is not None:
            if not isinstance(trim, str):
                raise TypeError("'trim' should be a 'str' object.")

        tmp = np.full(len(self._collection), True, dtype=bool)

        if len(kwargs) != 0:
            for key, val in kwargs.items():
                if key == 'filename':
                    file_mask = np.array([
                        fnmatch.fnmatch(filename, kwargs['filename'])
                        for filename in self._filenames_without_path
                    ],
                                         dtype=bool)

                    tmp = tmp & file_mask
                else:
                    tmp = tmp & (self._collection[key.upper()] == val)

        if np.count_nonzero(tmp) == 0:
            yield None

        x = self._collection[tmp]['NAXIS1'][0]
        y = self._collection[tmp]['NAXIS2'][0]
        shape = (y, x)

        mask = None
        if masks is not None:
            mask = make_mask(shape, masks)

        for filename in self._collection[tmp]['filename']:
            ccd = CCDData.read(filename,
                               unit=self._unit,
                               output_verify='silentfix+ignore')

            ccd.mask = mask
            ccd = trim_image(ccd, trim)

            if (self._gain is not None) and (self._read_noise is not None):
                data_with_deviation = create_deviation(
                    ccd,
                    gain=self._gain,
                    readnoise=self._read_noise,
                    disregard_nan=self._disregard_nan)

                gain_corrected = gain_correct(data_with_deviation, self._gain)

                yield gain_corrected
            else:
                yield ccd
Ejemplo n.º 16
0
    def __call__(self, collection=None, masks=None, trim=None, **kwargs):
        """
        Generator that yields each 'ccdproc.CCDData' objects in the collection.

        Parameters
        ----------
        collection : 'FitsCollection.collection' or optional
            Filtered collection.

        masks : str, list of str or optional
            Area to be masked.

        trim : str or optional
            Trim section.

        **kwargs :
            Any additional keywords are used to filter the items returned.

        Yields
        ------
        'ccdproc.CCDData'
            yield the next 'ccdproc.CCDData' in the collection.

        Examples
        --------
        >>> from tuglib.io import FitsCollection
        >>>
        >>> mask = '[:, 1000:1046]'
        >>> trim = '[100:1988, :]'
        >>>
        >>> images = FitsCollection(
                location='/home/user/data/fits/', gain=0.57, read_noise=4.11)
        >>>
        >>> query = images['EXPTIME'] == 100.0
        >>> sub_collections = images[query]
        >>>
        >>> ccds = images(sub_collections, masks=mask, trim=trim)
        """

        if masks is not None:
            if not isinstance(masks, (str, list, type(None))):
                raise TypeError(
                    "'masks' should be 'str', 'list' or 'None' object.")

        if trim is not None:
            if not isinstance(trim, str):
                raise TypeError("'trim' should be a 'str' object.")

        if collection is None:
            return self.ccds(masks=masks, trim=trim, **kwargs)

        tmp = np.full(len(collection), True, dtype=bool)

        if len(kwargs) != 0:
            for key, val in kwargs.items():
                tmp = tmp & (collection[key] == val)

        x = collection[tmp]['NAXIS1'][0]
        y = collection[tmp]['NAXIS2'][0]
        shape = (y, x)

        mask = None
        if masks is not None:
            mask = make_mask(shape, masks)

        if (self._gain is not None) and (self._read_noise is not None):
            for filename in collection[tmp]['filename']:
                ccd = CCDData.read(filename, unit=self._unit)

                ccd.mask = mask
                ccd = trim_image(ccd, trim)

                data_with_deviation = create_deviation(
                    ccd,
                    gain=self._gain,
                    readnoise=self._read_noise,
                    disregard_nan=self._disregard_nan)

                gain_corrected = gain_correct(data_with_deviation, self._gain)

                yield gain_corrected
        else:
            for filename in collection[tmp]['filename']:
                ccd = CCDData.read(filename, unit=self._unit)

                ccd.mask = mask
                ccd = trim_image(ccd, trim)

                yield ccd
Ejemplo n.º 17
0
def ccd_process(ccd,
                oscan=None,
                trim=None,
                error=False,
                masterbias=None,
                bad_pixel_mask=None,
                gain=None,
                rdnoise=None,
                oscan_median=True,
                oscan_model=None):
    """Perform basic processing on ccd data.

       The following steps can be included:
        * overscan correction
        * trimming of the image
        * create edeviation frame
        * gain correction
        * add a mask to the data
        * subtraction of master bias

       The task returns a processed `ccdproc.CCDData` object.

    Parameters
    ----------
    ccd: `ccdproc.CCDData`
        Frame to be reduced

    oscan: None, str, or, `~ccdproc.ccddata.CCDData`
        For no overscan correction, set to None.   Otherwise proivde a region
        of `ccd` from which the overscan is extracted, using the FITS
        conventions for index order and index start, or a
        slice from `ccd` that contains the overscan.

    trim: None or str
        For no trim correction, set to None.   Otherwise proivde a region
        of `ccd` from which the image should be trimmed, using the FITS
        conventions for index order and index start.

    error: boolean
        If True, create an uncertainty array for ccd

    masterbias: None, `~numpy.ndarray`,  or `~ccdproc.CCDData`
        A materbias frame to be subtracted from ccd.

    bad_pixel_mask: None or `~numpy.ndarray`
        A bad pixel mask for the data. The bad pixel mask should be in given
        such that bad pixels havea value of 1 and good pixels a value of 0.

    gain: None or `~astropy.Quantity`
        Gain value to multiple the image by to convert to electrons

    rdnoise: None or `~astropy.Quantity`
        Read noise for the observations.  The read noise should be in
        `~astropy.units.electron`


    oscan_median :  bool, optional
        If true, takes the median of each line.  Otherwise, uses the mean

    oscan_model :  `~astropy.modeling.Model`, optional
        Model to fit to the data.  If None, returns the values calculated
        by the median or the mean.

    Returns
    -------
    ccd: `ccdproc.CCDData`
        Reduded ccd

    Examples
    --------

    1. To overscan, trim, and gain correct a data set:

    >>> import numpy as np
    >>> from astropy import units as u
    >>> from hrsprocess import ccd_process
    >>> ccd = CCDData(np.ones([100, 100]), unit=u.adu)
    >>> nccd = ccd_process(ccd, oscan='[1:10,1:100]', trim='[10:100, 1,100]',
                           error=False, gain=2.0*u.electron/u.adu)


    """
    # make a copy of the object
    nccd = ccd.copy()

    # apply the overscan correction
    if isinstance(oscan, ccdproc.CCDData):
        nccd = ccdproc.subtract_overscan(nccd,
                                         overscan=oscan,
                                         median=oscan_median,
                                         model=oscan_model)
    elif isinstance(oscan, six.string_types):
        nccd = ccdproc.subtract_overscan(nccd,
                                         fits_section=oscan,
                                         median=oscan_median,
                                         model=oscan_model)
    elif oscan is None:
        pass
    else:
        raise TypeError('oscan is not None, a string, or CCDData object')

    # apply the trim correction
    if isinstance(trim, six.string_types):
        nccd = ccdproc.trim_image(nccd, fits_section=trim)
    elif trim is None:
        pass
    else:
        raise TypeError('trim is not None or a string')

    # create the error frame
    if error and gain is not None and rdnoise is not None:
        nccd = ccdproc.create_deviation(nccd, gain=gain, rdnoise=rdnoise)
    elif error and (gain is None or rdnoise is None):
        raise ValueError(
            'gain and rdnoise must be specified to create error frame')

    # apply the bad pixel mask
    if isinstance(bad_pixel_mask, np.ndarray):
        nccd.mask = bad_pixel_mask
    elif bad_pixel_mask is None:
        pass
    else:
        raise TypeError('bad_pixel_mask is not None or numpy.ndarray')

    # apply the gain correction
    if isinstance(gain, u.quantity.Quantity):
        nccd = ccdproc.gain_correct(nccd, gain)
    elif gain is None:
        pass
    else:
        raise TypeError('gain is not None or astropy.Quantity')

    # test subtracting the master bias
    if isinstance(masterbias, ccdproc.CCDData):
        nccd = ccdproc.subtract_bias(nccd, masterbias)
    elif isinstance(masterbias, np.ndarray):
        nccd.data = nccd.data - masterbias
    elif masterbias is None:
        pass
    else:
        raise TypeError(
            'masterbias is not None, numpy.ndarray,  or a CCDData object')

    return nccd