diff --git a/ccdproc/ccddata.py b/ccdproc/ccddata.py index f18dcfd2..a5b23a32 100644 --- a/ccdproc/ccddata.py +++ b/ccdproc/ccddata.py @@ -20,8 +20,7 @@ class CCDData(NDDataArray): - - """A class describing basic CCD data + """A class describing basic CCD data. The CCDData class is based on the NDData object and includes a data array, uncertainty frame, mask frame, meta data, units, and WCS information for a @@ -29,40 +28,52 @@ class CCDData(NDDataArray): Parameters ----------- - data : `~numpy.ndarray` or :class:`~ccdproc.CCDData` + data : `~ccdproc.CCDData`-like or `numpy.ndarray`-like The actual data contained in this `~ccdproc.CCDData` object. - Note that this will always be copies by *reference* , so you should - make copy the ``data`` before passing it in if that's the desired + Note that the data will always be saved by *reference*, so you should + make a copy of the ``data`` before passing it in if that's the desired behavior. - uncertainty : `~astropy.nddata.StdDevUncertainty` or `~numpy.ndarray`, - optional Uncertainties on the data. + uncertainty : `~astropy.nddata.StdDevUncertainty`, `numpy.ndarray` or \ + None, optional + Uncertainties on the data. + Default is ``None``. - mask : `~numpy.ndarray`, optional + mask : `numpy.ndarray` or None, optional Mask for the data, given as a boolean Numpy array with a shape matching that of the data. The values must be `False` where the data is *valid* and `True` when it is not (like Numpy masked arrays). If ``data`` is a numpy masked array, providing ``mask`` here will causes the mask from the masked array to be ignored. + Default is ``None``. - flags : `~numpy.ndarray` or `~astropy.nddata.FlagCollection`, optional + flags : `numpy.ndarray` or `~astropy.nddata.FlagCollection` or None, \ + optional Flags giving information about each pixel. These can be specified either as a Numpy array of any type with a shape matching that of the data, or as a `~astropy.nddata.FlagCollection` instance which has a shape matching that of the data. + Default is ``None``. - wcs : `~astropy.wcs.WCS` object, optional + wcs : `~astropy.wcs.WCS` or None, optional WCS-object containing the world coordinate system for the data. + Default is ``None``. - meta : `dict`-like object, optional - Metadata for this object. "Metadata" here means all information that + meta : dict-like object or None, optional + Metadata for this object. "Metadata" here means all information that is included with this object but not part of any other attribute - of this particular object. e.g., creation date, unique identifier, + of this particular object, e.g. creation date, unique identifier, simulation parameters, exposure time, telescope name, etc. - unit : `~astropy.units.Unit` instance or str, optional + unit : `~astropy.units.Unit` or str, optional The units of the data. + Default is ``None``. + + .. warning:: + + If the unit is ``None`` or not otherwise specified it will raise a + ``ValueError`` Raises ------ @@ -81,11 +92,10 @@ class CCDData(NDDataArray): This method uses :func:`fits_ccddata_writer` with the provided parameters. - Notes ----- `~ccdproc.CCDData` objects can be easily converted to a regular - Numpy array using `numpy.asarray` + Numpy array using `numpy.asarray`. For example:: @@ -98,9 +108,9 @@ class CCDData(NDDataArray): This is useful, for example, when plotting a 2D image using matplotlib. - >>> from ccdproc import CCDData + >>> from ccdproc import CCDData >>> from matplotlib import pyplot as plt # doctest: +SKIP - >>> x = CCDData([[1,2,3], [4,5,6]], unit='adu') + >>> x = CCDData([[1,2,3], [4,5,6]], unit='adu') >>> plt.imshow(x) # doctest: +SKIP """ @@ -108,11 +118,11 @@ def __init__(self, *args, **kwd): if 'meta' not in kwd: kwd['meta'] = kwd.pop('header', None) if 'header' in kwd: - raise ValueError("Can't have both header and meta") + raise ValueError("can't have both header and meta.") super(CCDData, self).__init__(*args, **kwd) if self.unit is None: - raise ValueError("Unit for CCDData must be specified") + raise ValueError("a unit for CCDData must be specified.") @property def data(self): @@ -173,7 +183,8 @@ def meta(self, value): if hasattr(value, 'keys'): self._meta = value else: - raise TypeError('CCDData meta attribute must be dict-like') + raise TypeError( + 'the meta attribute of CCDData must be dict-like.') @property def uncertainty(self): @@ -186,13 +197,13 @@ def uncertainty(self, value): self._uncertainty = value elif isinstance(value, np.ndarray): if value.shape != self.shape: - raise ValueError("Uncertainty must have same shape as " - "data") + raise ValueError("uncertainty must have same shape as " + "data.") self._uncertainty = StdDevUncertainty(value) - log.info("Array provided for uncertainty; assuming it is a " + log.info("array provided for uncertainty; assuming it is a " "StdDevUncertainty.") else: - raise TypeError("Uncertainty must be an instance of a " + raise TypeError("uncertainty must be an instance of a " "NDUncertainty object or a numpy array.") self._uncertainty._parent_nddata = self else: @@ -202,32 +213,31 @@ def to_hdu(self, hdu_mask='MASK', hdu_uncertainty='UNCERT', hdu_flags=None): """Creates an HDUList object from a CCDData object. - Parameters - ---------- - hdu_mask, hdu_uncertainty, hdu_flags : str or None, optional - If it is a string append this attribute to the HDUList as - `~astropy.io.fits.ImageHDU` with the string as extension name. - Flags are not supported at this time. If ``None`` this attribute - is not appended. - Default is ``'MASK'`` for mask, ``'UNCERT'`` for uncertainty and - ``None`` for flags. - - Raises - ------- - ValueError - - If ``self.mask`` is set but not a `~numpy.ndarray`. - - If ``self.uncertainty`` is set but not a - `~astropy.nddata.StdDevUncertainty`. - - If ``self.uncertainty`` is set but has another unit then - ``self.data``. - - NotImplementedError - Saving flags is not supported. - - Returns - ------- - hdulist : astropy.io.fits.HDUList object - + Parameters + ---------- + hdu_mask, hdu_uncertainty, hdu_flags : str or None, optional + If it is a string append this attribute to the HDUList as + `~astropy.io.fits.ImageHDU` with the string as extension name. + Flags are not supported at this time. If ``None`` this attribute + is not appended. + Default is ``'MASK'`` for mask, ``'UNCERT'`` for uncertainty and + ``None`` for flags. + + Raises + ------- + ValueError + - If ``self.mask`` is set but not a `numpy.ndarray`. + - If ``self.uncertainty`` is set but not a + `~astropy.nddata.StdDevUncertainty`. + - If ``self.uncertainty`` is set but has another unit then + ``self.data``. + + NotImplementedError + Saving flags is not supported. + + Returns + ------- + hdulist : `~astropy.io.fits.HDUList` """ if isinstance(self.header, fits.Header): # Copy here so that we can modify the HDU header by adding WCS @@ -266,7 +276,7 @@ def to_hdu(self, hdu_mask='MASK', hdu_uncertainty='UNCERT', # Always assuming that the mask is a np.ndarray (check that it has # a 'shape'). if not hasattr(self.mask, 'shape'): - raise ValueError('Only a numpy.ndarray mask can be saved.') + raise ValueError('only a numpy.ndarray mask can be saved.') # Convert boolean mask to uint since io.fits cannot handle bool. hduMask = fits.ImageHDU(self.mask.astype(np.uint8), name=hdu_mask) @@ -277,7 +287,7 @@ def to_hdu(self, hdu_mask='MASK', hdu_uncertainty='UNCERT', # used so that loading the HDUList can infer the uncertainty type. # No idea how this can be done so only allow StdDevUncertainty. if self.uncertainty.__class__.__name__ != 'StdDevUncertainty': - raise ValueError('Only StdDevUncertainty can be saved.') + raise ValueError('only StdDevUncertainty can be saved.') # Assuming uncertainty is an StdDevUncertainty save just the array # this might be problematic if the Uncertainty has a unit differing @@ -286,15 +296,15 @@ def to_hdu(self, hdu_mask='MASK', hdu_uncertainty='UNCERT', if (hasattr(self.uncertainty, 'unit') and self.uncertainty.unit is not None and self.uncertainty.unit != self.unit): - raise ValueError('Saving uncertainties with a unit differing' - 'from the data unit is not supported') + raise ValueError('saving uncertainties with a unit differing' + 'from the data unit is not supported.') hduUncert = fits.ImageHDU(self.uncertainty.array, name=hdu_uncertainty) hdus.append(hduUncert) if hdu_flags and self.flags: - raise NotImplementedError('Adding the flags to a HDU is not ' + raise NotImplementedError('adding the flags to a HDU is not ' 'supported at this time.') hdulist = fits.HDUList(hdus) @@ -309,9 +319,9 @@ def copy(self): def _ccddata_arithmetic(self, other, operation, scale_uncertainty=False): """ - Perform the common parts of arithmetic operations on CCDData objects + Perform the common parts of arithmetic operations on CCDData objects. - This should only be called when ``other`` is a Quantity or a number + This should only be called when ``other`` is a Quantity or a number. """ # THE "1 *" IS NECESSARY to get the right result, at least in # astropy-0.4dev. Using the np.multiply, etc, methods with a Unit @@ -329,7 +339,7 @@ def _ccddata_arithmetic(self, other, operation, scale_uncertainty=False): elif isinstance(other, numbers.Number): other_value = other else: - raise TypeError("Cannot do arithmetic with type '{0}' " + raise TypeError("cannot do arithmetic with type '{0}' " "and 'CCDData'".format(type(other))) result_unit = operation(1 * self.unit, other).unit @@ -369,10 +379,11 @@ def multiply(self, other, compare_wcs='first_found'): return result else: if hasattr(self, '_arithmetics_wcs'): - return super(CCDData, self).multiply(other, - compare_wcs=compare_wcs) + return super(CCDData, self).multiply( + other, compare_wcs=compare_wcs) else: - raise ImportError("wcs_compare functionality requires astropy 1.2 or greater") + raise ImportError("wcs_compare functionality requires " + "astropy 1.2 or greater.") return self._ccddata_arithmetic(other, np.multiply, scale_uncertainty=True) @@ -395,10 +406,11 @@ def divide(self, other, compare_wcs='first_found'): return result else: if hasattr(self, '_arithmetics_wcs'): - return super(CCDData, self).divide(other, - compare_wcs=compare_wcs) + return super(CCDData, self).divide( + other, compare_wcs=compare_wcs) else: - raise ImportError("wcs_compare functionality requires astropy 1.2 or greater") + raise ImportError("wcs_compare functionality requires " + "astropy 1.2 or greater.") return self._ccddata_arithmetic(other, np.divide, scale_uncertainty=True) @@ -421,10 +433,11 @@ def add(self, other, compare_wcs='first_found'): return result else: if hasattr(self, '_arithmetics_wcs'): - return super(CCDData, self).add(other, - compare_wcs=compare_wcs) + return super(CCDData, self).add( + other, compare_wcs=compare_wcs) else: - raise ImportError("wcs_compare functionality requires astropy 1.2 or greater") + raise ImportError("wcs_compare functionality requires " + "astropy 1.2 or greater.") return self._ccddata_arithmetic(other, np.add, scale_uncertainty=False) @@ -448,10 +461,11 @@ def subtract(self, other, compare_wcs='first_found'): else: if hasattr(self, '_arithmetics_wcs'): - return super(CCDData, self).subtract(other, - compare_wcs=compare_wcs) + return super(CCDData, self).subtract( + other, compare_wcs=compare_wcs) else: - raise ImportError("wcs_compare functionality requires astropy 1.2 or greater") + raise ImportError("wcs_compare functionality requires " + "astropy 1.2 or greater.") return self._ccddata_arithmetic(other, np.subtract, scale_uncertainty=False) @@ -462,7 +476,6 @@ def _insert_in_metadata_fits_safe(self, key, value): Parameters ---------- - key : str Key to be inserted in dictionary. @@ -471,7 +484,6 @@ def _insert_in_metadata_fits_safe(self, key, value): Notes ----- - This addresses a shortcoming of the FITS standard. There are length restrictions on both the ``key`` (8 characters) and ``value`` (72 characters) in the FITS standard. There is a convention for handline @@ -505,19 +517,20 @@ def fits_ccddata_reader(filename, hdu=0, unit=None, hdu_uncertainty='UNCERT', Parameters ---------- - filename : str Name of fits file. hdu : int, optional - FITS extension from which CCDData should be initialized. If zero and + FITS extension from which CCDData should be initialized. If zero and and no data in the primary extention, it will search for the first - extension with data. The header will be added to the primary header. + extension with data. The header will be added to the primary header. + Default is ``0``. - unit : astropy.units.Unit, optional + unit : `~astropy.units.Unit`, optional Units of the image data. If this argument is provided and there is a unit for the image in the FITS header (the keyword ``BUNIT`` is used as the unit, if present), this argument is used for the unit. + Default is ``None``. hdu_uncertainty : str or None, optional FITS extension from which the uncertainty should be initialized. If the @@ -539,7 +552,6 @@ def fits_ccddata_reader(filename, hdu=0, unit=None, hdu_uncertainty='UNCERT', Notes ----- - FITS files that contained scaled data (e.g. unsigned integer images) will be scaled and the keywords used to manage scaled data in :mod:`astropy.io.fits` are disabled. @@ -551,7 +563,7 @@ def fits_ccddata_reader(filename, hdu=0, unit=None, hdu_uncertainty='UNCERT', } for key, msg in unsupport_open_keywords.items(): if key in kwd: - prefix = 'Unsupported keyword: {0}.'.format(key) + prefix = 'unsupported keyword: {0}.'.format(key) raise TypeError(' '.join([prefix, msg])) with fits.open(filename, **kwd) as hdus: hdr = hdus[hdu].header @@ -568,15 +580,18 @@ def fits_ccddata_reader(filename, hdu=0, unit=None, hdu_uncertainty='UNCERT', mask = None if hdu_flags in hdus: - raise NotImplementedError('Loading flags is currently not supported.') + raise NotImplementedError('loading flags is currently not ' + 'supported.') - # search for the first instance with data if the primary header is empty + # search for the first instance with data if + # the primary header is empty. if hdu == 0 and hdus[hdu].data is None: for i in range(len(hdus)): if hdus.fileinfo(i)['datSpan'] > 0: hdu = i hdr = hdr + hdus[hdu].header - log.info("First HDU with data is exention {0}.".format(hdu)) + log.info("first HDU with data is exention " + "{0}.".format(hdu)) break if 'bunit' in hdr: @@ -589,7 +604,7 @@ def fits_ccddata_reader(filename, hdu=0, unit=None, hdu_uncertainty='UNCERT', fits_unit_string = None if unit is not None and fits_unit_string: - log.info("Using the unit {0} passed to the FITS reader instead of " + log.info("using the unit {0} passed to the FITS reader instead of " "the unit {1} in the FITS file.".format(unit, fits_unit_string)) @@ -613,9 +628,8 @@ def fits_ccddata_writer(ccd_data, filename, hdu_mask='MASK', Parameters ---------- - filename : str - Name of file + Name of file. hdu_mask, hdu_uncertainty, hdu_flags : str or None, optional If it is a string append this attribute to the HDUList as @@ -631,7 +645,7 @@ def fits_ccddata_writer(ccd_data, filename, hdu_mask='MASK', Raises ------- ValueError - - If ``self.mask`` is set but not a `~numpy.ndarray`. + - If ``self.mask`` is set but not a `numpy.ndarray`. - If ``self.uncertainty`` is set but not a `~astropy.nddata.StdDevUncertainty`. - If ``self.uncertainty`` is set but has another unit then diff --git a/ccdproc/combiner.py b/ccdproc/combiner.py index 7fa41995..ff76d42c 100644 --- a/ccdproc/combiner.py +++ b/ccdproc/combiner.py @@ -18,31 +18,33 @@ class Combiner(object): + """ + A class for combining CCDData objects. - """A class for combining CCDData objects. - - The Combiner class is used to combine together CCDData objects + The Combiner class is used to combine together `~ccdproc.CCDData` objects including the method for combining the data, rejecting outlying data, - and weighting used for combining frames + and weighting used for combining frames. Parameters ----------- - ccd_list : `list` + ccd_list : list A list of CCDData objects that will be combined together. - dtype : 'numpy dtype' - Allows user to set dtype. + dtype : str or `numpy.dtype` or None, optional + Allows user to set dtype. See `numpy.array` ``dtype`` parameter + description. + Default is ``None``. Raises ------ TypeError If the ``ccd_list`` are not `~ccdproc.CCDData` objects, have different - units, or are different shapes + units, or are different shapes. Notes ----- The following is an example of combining together different - `~ccdproc.CCDData` objects: + `~ccdproc.CCDData` objects:: >>> import numpy as np >>> import astropy.units as u @@ -57,12 +59,10 @@ class Combiner(object): [ 0.66666667, 0.66666667, 0.66666667, 0.66666667], [ 0.66666667, 0.66666667, 0.66666667, 0.66666667], [ 0.66666667, 0.66666667, 0.66666667, 0.66666667]]) - """ - def __init__(self, ccd_list, dtype=None): if ccd_list is None: - raise TypeError("ccd_list should be a list of CCDData objects") + raise TypeError("ccd_list should be a list of CCDData objects.") if dtype is None: dtype = np.float64 @@ -70,35 +70,36 @@ def __init__(self, ccd_list, dtype=None): default_shape = None default_unit = None for ccd in ccd_list: - #raise an error if the objects aren't CCDDAata objects + # raise an error if the objects aren't CCDDAata objects if not isinstance(ccd, CCDData): - raise TypeError("ccd_list should only contain CCDData objects") + raise TypeError( + "ccd_list should only contain CCDData objects.") - #raise an error if the shape is different + # raise an error if the shape is different if default_shape is None: default_shape = ccd.shape else: if not (default_shape == ccd.shape): - raise TypeError("CCDData objects are not the same size") + raise TypeError("CCDData objects are not the same size.") - #raise an error if the units are different + # raise an error if the units are different if default_unit is None: default_unit = ccd.unit else: if not (default_unit == ccd.unit): - raise TypeError("CCDdata objects are not the same unit") + raise TypeError("CCDData objects don't the same unit.") self.ccd_list = ccd_list self.unit = default_unit self.weights = None self._dtype = dtype - #set up the data array + # set up the data array ydim, xdim = default_shape new_shape = (len(ccd_list), ydim, xdim) self.data_arr = ma.masked_all(new_shape, dtype=dtype) - #populate self.data_arr + # populate self.data_arr for i, ccd in enumerate(ccd_list): self.data_arr[i] = ccd.data if ccd.mask is not None: @@ -119,15 +120,12 @@ def weights(self): """ Weights used when combining the `~ccdproc.CCDData` objects. - Parameters ---------- - - weight_values : `~numpy.ndarray` + weight_values : `numpy.ndarray` or None An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined. """ - return self._weights @weights.setter @@ -137,9 +135,10 @@ def weights(self, value): if value.shape == self.data_arr.data.shape: self._weights = value else: - raise ValueError("dimensions of weights do not match data") + raise ValueError( + "dimensions of weights do not match data.") else: - raise TypeError("mask must be a Numpy array") + raise TypeError("mask must be a numpy.ndarray.") else: self._weights = None @@ -150,15 +149,12 @@ def scaling(self): Parameters ---------- - - scale : function or array-like or None, optional + scale : function or `numpy.ndarray`-like or None, optional Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a list or array whose length is the number of images in the `~ccdproc.Combiner`. - Default is `None`. """ - return self._scaling @scaling.setter @@ -176,23 +172,24 @@ def scaling(self, value): len(value) == n_images self._scaling = np.array(value) except TypeError: - raise TypeError("Scaling must be a function or an array " + raise TypeError("scaling must be a function or an array " "the same length as the number of images.") # reshape so that broadcasting occurs properly self._scaling = self.scaling[:, np.newaxis, np.newaxis] - #set up min/max clipping algorithms + # set up min/max clipping algorithms def minmax_clipping(self, min_clip=None, max_clip=None): """Mask all pixels that are below min_clip or above max_clip. Parameters ----------- - min_clip : None or float - If specified, all pixels with values below min_clip will be masked - - max_clip : None or float - If specified, all pixels with values above min_clip will be masked + min_clip : float or None, optional + If not None, all pixels with values below min_clip will be masked. + Default is ``None``. + max_clip : float or None, optional + If not None, all pixels with values above min_clip will be masked. + Default is ``None``. """ if min_clip is not None: mask = (self.data_arr < min_clip) @@ -201,45 +198,48 @@ def minmax_clipping(self, min_clip=None, max_clip=None): mask = (self.data_arr > max_clip) self.data_arr.mask[mask] = True - #set up sigma clipping algorithms + # set up sigma clipping algorithms def sigma_clipping(self, low_thresh=3, high_thresh=3, func=ma.mean, dev_func=ma.std): - """Pixels will be rejected if they have deviations greater than those - set by the threshold values. The algorithm will first calculated - a baseline value using the function specified in func and deviation - based on dev_func and the input data array. Any pixel with a - deviation from the baseline value greater than that set by - high_thresh or lower than that set by low_thresh will be rejected. + """ + Pixels will be rejected if they have deviations greater than those + set by the threshold values. The algorithm will first calculated + a baseline value using the function specified in func and deviation + based on dev_func and the input data array. Any pixel with a + deviation from the baseline value greater than that set by + high_thresh or lower than that set by low_thresh will be rejected. Parameters ----------- - low_thresh : positive float or None + low_thresh : positive float or None, optional Threshold for rejecting pixels that deviate below the baseline - value. If negative value, then will be convert to a positive - value. If None, no rejection will be done based on low_thresh. + value. If negative value, then will be convert to a positive + value. If None, no rejection will be done based on low_thresh. + Default is 3. - high_thresh : positive float or None + high_thresh : positive float or None, optional Threshold for rejecting pixels that deviate above the baseline value. If None, no rejection will be done based on high_thresh. + Default is 3. - func : function - Function for calculating the baseline values (i.e. mean or median). - This should be a function that can handle - numpy.ma.core.MaskedArray objects. + func : function, optional + Function for calculating the baseline values (i.e. `numpy.ma.mean` + or `numpy.ma.median`). This should be a function that can handle + `numpy.ma.MaskedArray` objects. + Default is `numpy.ma.mean`. - dev_func : function + dev_func : function, optional Function for calculating the deviation from the baseline value - (i.e. std). This should be a function that can handle - numpy.ma.core.MaskedArray objects. - + (i.e. `numpy.ma.std`). This should be a function that can handle + `numpy.ma.MaskedArray` objects. + Default is `numpy.ma.std`. """ - #check for negative numbers in low_thresh - - #setup baseline values + # setup baseline values baseline = func(self.data_arr, axis=0) dev = dev_func(self.data_arr, axis=0) - #reject values + # reject values if low_thresh is not None: + # check for negative numbers in low_thresh if low_thresh < 0: low_thresh = abs(low_thresh) mask = (self.data_arr - baseline < -low_thresh * dev) @@ -249,40 +249,42 @@ def sigma_clipping(self, low_thresh=3, high_thresh=3, self.data_arr.mask[mask] = True # set up the combining algorithms - def median_combine(self, median_func=ma.median, scale_to=None, uncertainty_func=sigma_func): - """Median combine a set of arrays. - - A `~ccdproc.CCDData` object is returned - with the data property set to the median of the arrays. If the data - was masked or any data have been rejected, those pixels will not be - included in the median. A mask will be returned, and if a pixel - has been rejected in all images, it will be masked. The - uncertainty of the combined image is set by 1.4826 times the median - absolute deviation of all input images. - - Parameters - ---------- - median_func : function, optional - Function that calculates median of a `~numpy.ma.masked_array`. - Default is to use `numpy.ma.median` to calculate median. - - scale_to : float, optional - Scaling factor used in the average combined image. If given, - it overrides ``CCDData.scaling``. Defaults to None. - - uncertainty_func : function, optional - Function to calculate uncertainty. Defaults to `ccdproc.sigma_func` - - Returns - ------- - combined_image: `~ccdproc.CCDData` - CCDData object based on the combined input of CCDData objects. - - Warnings - -------- - The uncertainty currently calculated using the median absolute - deviation does not account for rejected pixels. + def median_combine(self, median_func=ma.median, scale_to=None, + uncertainty_func=sigma_func): + """ + Median combine a set of arrays. + + A `~ccdproc.CCDData` object is returned with the data property set to + the median of the arrays. If the data was masked or any data have been + rejected, those pixels will not be included in the median. A mask will + be returned, and if a pixel has been rejected in all images, it will be + masked. The uncertainty of the combined image is set by 1.4826 times + the median absolute deviation of all input images. + Parameters + ---------- + median_func : function, optional + Function that calculates median of a `numpy.ma.MaskedArray`. + Default is `numpy.ma.median`. + + scale_to : float or None, optional + Scaling factor used in the average combined image. If given, + it overrides `scaling`. + Defaults to None. + + uncertainty_func : function, optional + Function to calculate uncertainty. + Defaults is `~ccdproc.sigma_func`. + + Returns + ------- + combined_image: `~ccdproc.CCDData` + CCDData object based on the combined input of CCDData objects. + + Warnings + -------- + The uncertainty currently calculated using the median absolute + deviation does not account for rejected pixels. """ if scale_to is not None: scalings = scale_to @@ -317,35 +319,36 @@ def median_combine(self, median_func=ma.median, scale_to=None, uncertainty_func= # return the combined image return combined_image - def average_combine(self, scale_func=ma.average, scale_to=None, uncertainty_func=ma.std): - """ Average combine together a set of arrays. - - A `~ccdproc.CCDData` object is returned with the data property - set to the average of the arrays. If the data was masked or any - data have been rejected, those pixels will not be included in the - average. A mask will be returned, and if a pixel has been - rejected in all images, it will be masked. The uncertainty of - the combined image is set by the standard deviation of the input - images. + def average_combine(self, scale_func=ma.average, scale_to=None, + uncertainty_func=ma.std): + """ + Average combine together a set of arrays. - Parameters - ---------- - scale_func : function, optional - Function to calculate the average. Defaults to - `~numpy.ma.average`. + A `~ccdproc.CCDData` object is returned with the data property + set to the average of the arrays. If the data was masked or any + data have been rejected, those pixels will not be included in the + average. A mask will be returned, and if a pixel has been + rejected in all images, it will be masked. The uncertainty of + the combined image is set by the standard deviation of the input + images. - scale_to : float, optional - Scaling factor used in the average combined image. If given, - it overrides ``CCDData.scaling``. Defaults to None. + Parameters + ---------- + scale_func : function, optional + Function to calculate the average. Defaults to + `numpy.ma.average`. - uncertainty_func: function, optional - Function to calculate uncertainty. Defaults to `numpy.ma.std` + scale_to : float or None, optional + Scaling factor used in the average combined image. If given, + it overrides `scaling`. Defaults to ``None``. - Returns - ------- - combined_image: `~ccdproc.CCDData` - CCDData object based on the combined input of CCDData objects. + uncertainty_func : function, optional + Function to calculate uncertainty. Defaults to `numpy.ma.std`. + Returns + ------- + combined_image: `~ccdproc.CCDData` + CCDData object based on the combined input of CCDData objects. """ if scale_to is not None: scalings = scale_to @@ -380,95 +383,97 @@ def average_combine(self, scale_func=ma.average, scale_to=None, uncertainty_func return combined_image -def combine(img_list, output_file=None, method='average', weights=None, scale=None, mem_limit=16e9, +def combine(img_list, output_file=None, method='average', weights=None, + scale=None, mem_limit=16e9, minmax_clip=False, minmax_clip_min=None, minmax_clip_max=None, - sigma_clip=False, sigma_clip_low_thresh=3, sigma_clip_high_thresh=3, + sigma_clip=False, + sigma_clip_low_thresh=3, sigma_clip_high_thresh=3, sigma_clip_func=ma.mean, sigma_clip_dev_func=ma.std, **ccdkwargs): - - """Convenience function for combining multiple images + """ + Convenience function for combining multiple images. Parameters ----------- - img_list : `list`, 'string' - A list of fits filenames or CCDData objects that will be combined together. - Or a string of fits filenames seperated by comma ",". + img_list : list or str + A list of fits filenames or `~ccdproc.CCDData` objects that will be + combined together. Or a string of fits filenames seperated by comma + ",". - output_file: 'string', optional - Optional output fits filename to which the final output can be directly written. + output_file : str or None, optional + Optional output fits filename to which the final output can be directly + written. + Default is ``None``. - method: 'string' (default average) - Method to combine images. - 'average' : To combine by calculating average - 'median' : To combine by calculating median + method : str, optional + Method to combine images: - weights: `~numpy.ndarray`, optional + - ``'average'`` : To combine by calculating the average. + - ``'median'`` : To combine by calculating the median. + + Default is ``'average'``. + + weights : `numpy.ndarray` or None, optional Weights to be used when combining images. An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined. + Default is ``None``. - scale : function or array-like or None, optional + scale : function or `numpy.ndarray`-like or None, optional Scaling factor to be used when combining images. Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a list or array whose length is the number of images in the `Combiner`. Default is ``None``. - mem_limit : float (default 16e9) + mem_limit : float, optional Maximum memory which should be used while combining (in bytes). + Default is ``16e9``. - minmax_clip : Boolean (default False) - Set to True if you want to mask all pixels that are below minmax_clip_min or above minmax_clip_max before combining. + minmax_clip : bool, optional + Set to True if you want to mask all pixels that are below + minmax_clip_min or above minmax_clip_max before combining. + Default is ``False``. - Parameters below are valid only when minmax_clip is set to True. + Parameters below are valid only when minmax_clip is set to True, see + :meth:`Combiner.minmax_clipping` for the parameter description: - minmax_clip_min: None, float - All pixels with values below minmax_clip_min will be masked. - minmax_clip_max: None or float - All pixels with values above minmax_clip_max will be masked. + - ``minmax_clip_min`` : float or None, optional + - ``minmax_clip_max`` : float or None, optional - sigma_clip : Boolean (default False) - Set to True if you want to reject pixels which have deviations greater than those - set by the threshold values. The algorithm will first calculated + sigma_clip : bool, optional + Set to True if you want to reject pixels which have deviations greater + than those + set by the threshold values. The algorithm will first calculated a baseline value using the function specified in func and deviation - based on sigma_clip_dev_func and the input data array. Any pixel with a - deviation from the baseline value greater than that set by - sigma_clip_high_thresh or lower than that set by sigma_clip_low_thresh will be rejected. + based on sigma_clip_dev_func and the input data array. Any pixel with + a deviation from the baseline value greater than that set by + sigma_clip_high_thresh or lower than that set by sigma_clip_low_thresh + will be rejected. + Default is ``False``. - Parameters below are valid only when sigma_clip is set to True. + Parameters below are valid only when sigma_clip is set to True. See + :meth:`Combiner.sigma_clipping` for the parameter description. - sigma_clip_low_thresh : positive float or None - Threshold for rejecting pixels that deviate below the baseline - value. If negative value, then will be convert to a positive - value. If None, no rejection will be done based on sigma_clip_low_thresh. + - ``sigma_clip_low_thresh`` : positive float or None, optional + - ``sigma_clip_high_thresh`` : positive float or None, optional + - ``sigma_clip_func`` : function, optional + - ``sigma_clip_dev_func`` : function, optional - sigma_clip_high_thresh : positive float or None - Threshold for rejecting pixels that deviate above the baseline - value. If None, no rejection will be done based on sigma_clip_high_thresh. - - sigma_clip_func : function - Function for calculating the baseline values (i.e. mean or median). - This should be a function that can handle - numpy.ma.core.MaskedArray objects. - - sigma_clip_dev_func : function - Function for calculating the deviation from the baseline value - (i.e. std). This should be a function that can handle - numpy.ma.core.MaskedArray objects. - - **ccdkwargs: Other keyword arguments for CCD Object's fits reader. + ccdkwargs : Other keyword arguments for `ccdproc.fits_ccddata_reader`. Returns ------- - combined_image: `~ccdproc.CCDData` + combined_image : `~ccdproc.CCDData` CCDData object based on the combined input of CCDData objects. - """ - if not isinstance(img_list,list): - # If not a list, check wheter it is a string of filenames seperated by comma - if isinstance(img_list,str) and (',' in img_list): + if not isinstance(img_list, list): + # If not a list, check wheter it is a string of filenames seperated + # by comma + if isinstance(img_list, str) and (',' in img_list): img_list = img_list.split(',') else: - raise ValueError("Unrecognised input for list of images to combine.") + raise ValueError( + "unrecognised input for list of images to combine.") # Select Combine function to call in Combiner if method == 'average': @@ -476,15 +481,14 @@ def combine(img_list, output_file=None, method='average', weights=None, scale=No elif method == 'median': combine_function = 'median_combine' else: - raise ValueError("Unrecognised combine method : {0}".format(method)) - + raise ValueError("unrecognised combine method : {0}.".format(method)) # First we create a CCDObject from first image for storing output - if isinstance(img_list[0],CCDData): + if isinstance(img_list[0], CCDData): ccd = img_list[0].copy() else: # User has provided fits filenames to read from - ccd = CCDData.read(img_list[0],**ccdkwargs) + ccd = CCDData.read(img_list[0], **ccdkwargs) size_of_an_img = ccd.data.nbytes if ccd.uncertainty is not None: @@ -496,22 +500,24 @@ def combine(img_list, output_file=None, method='average', weights=None, scale=No no_of_img = len(img_list) - #determine the number of chunks to split the images into + # determine the number of chunks to split the images into no_chunks = int((size_of_an_img*no_of_img)/mem_limit)+1 - log.info('Splitting each image into %d chunks to limit memory usage to %d bytes.', - no_chunks, mem_limit) + log.info('splitting each image into {0} chunks to limit memory usage to ' + '{1} bytes.'.format(no_chunks, mem_limit)) xs, ys = ccd.data.shape # First we try to split only along fast x axis xstep = max(1, int(xs/no_chunks)) - # If more chunks need to be created we split in Y axis for remaining number of chunks - ystep = max(1, int(ys/(1+ no_chunks - int(xs/xstep)) ) ) + # If more chunks need to be created we split in Y axis for remaining number + # of chunks + ystep = max(1, int(ys / (1 + no_chunks - int(xs / xstep)))) - # Dictionary of Combiner properties to set and methods to call before combining + # Dictionary of Combiner properties to set and methods to call before + # combining to_set_in_combiner = {} to_call_in_combiner = {} - # Define all the Combiner properties one wants to apply before combining images - + # Define all the Combiner properties one wants to apply before combining + # images if weights is not None: to_set_in_combiner['weights'] = weights @@ -521,10 +527,10 @@ def combine(img_list, output_file=None, method='average', weights=None, scale=No if callable(scale): scalevalues = [] for image in img_list: - if isinstance(image,CCDData): + if isinstance(image, CCDData): imgccd = image else: - imgccd = CCDData.read(image,**ccdkwargs) + imgccd = CCDData.read(image, **ccdkwargs) scalevalues.append(scale(imgccd.data)) @@ -532,31 +538,30 @@ def combine(img_list, output_file=None, method='average', weights=None, scale=No else: to_set_in_combiner['scaling'] = scale - if minmax_clip: - to_call_in_combiner['minmax_clipping'] = {'min_clip':minmax_clip_min, - 'max_clip':minmax_clip_max} + to_call_in_combiner['minmax_clipping'] = {'min_clip': minmax_clip_min, + 'max_clip': minmax_clip_max} if sigma_clip: - to_call_in_combiner['sigma_clipping'] = {'low_thresh':sigma_clip_low_thresh, - 'high_thresh':sigma_clip_high_thresh, - 'func':sigma_clip_func, - 'dev_func':sigma_clip_dev_func} + to_call_in_combiner['sigma_clipping'] = { + 'low_thresh': sigma_clip_low_thresh, + 'high_thresh': sigma_clip_high_thresh, + 'func': sigma_clip_func, + 'dev_func': sigma_clip_dev_func} # Finally Run the input method on all the subsections of the image # and write final stitched image to ccd - - for x in range(0,xs,xstep): - for y in range(0,ys,ystep): + for x in range(0, xs, xstep): + for y in range(0, ys, ystep): xend, yend = min(xs, x + xstep), min(ys, y + ystep) ccd_list = [] for image in img_list: - if isinstance(image,CCDData): + if isinstance(image, CCDData): imgccd = image else: - imgccd = CCDData.read(image,**ccdkwargs) + imgccd = CCDData.read(image, **ccdkwargs) - #Trim image + # Trim image ccd_list.append(trim_image(imgccd[x:xend, y:yend])) # Create Combiner for tile @@ -570,7 +575,7 @@ def combine(img_list, output_file=None, method='average', weights=None, scale=No # Finally call the combine algorithm comb_tile = getattr(tile_combiner, combine_function)() - #add it back into the master image + # add it back into the master image ccd.data[x:xend, y:yend] = comb_tile.data if ccd.mask is not None: ccd.mask[x:xend, y:yend] = comb_tile.mask diff --git a/ccdproc/core.py b/ccdproc/core.py index 1fd05f59..1bb8d5f4 100644 --- a/ccdproc/core.py +++ b/ccdproc/core.py @@ -47,113 +47,129 @@ @log_to_metadata def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, - dark_frame=None, master_flat=None, bad_pixel_mask=None, gain=None, - readnoise=None, oscan_median=True, oscan_model=None, + dark_frame=None, master_flat=None, bad_pixel_mask=None, + gain=None, readnoise=None, oscan_median=True, oscan_model=None, min_value=None, dark_exposure=None, data_exposure=None, exposure_key=None, exposure_unit=None, dark_scale=False): """Perform basic processing on ccd data. The following steps can be included: - * overscan correction - * trimming of the image - * create deviation frame - * gain correction + + * overscan correction (:func:`subtract_overscan`) + * trimming of the image (:func:`trim_image`) + * create deviation frame (:func:`create_deviation`) + * gain correction (:func:`gain_correct`) * add a mask to the data - * subtraction of master bias - * subtraction of a dark frame - * correction of flat field + * subtraction of master bias (:func:`subtract_bias`) + * subtraction of a dark frame (:func:`subtract_dark`) + * correction of flat field (:func:`flat_correct`) - The task returns a processed `ccdproc.CCDData` object. + The task returns a processed `~ccdproc.CCDData` object. Parameters ---------- - ccd: `~ccdproc.CCDData` - Frame to be reduced + 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 + oscan : `~ccdproc.ccddata.CCDData`, str or None, optional + 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. + Default is ``None``. - trim: None or str - For no trim correction, set to None. Otherwise proivde a region + trim : str or None, optional + 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. + Default is ``None``. - error: boolean - If True, create an uncertainty array for ccd + error : bool, optional + If True, create an uncertainty array for ccd. + Default is ``False``. - master_bias: None or `~ccdproc.CCDData` + master_bias : `~ccdproc.CCDData` or None, optional A master bias frame to be subtracted from ccd. + Default is ``None``. - dark_frame: None or `~ccdproc.CCDData` + dark_frame : `~ccdproc.CCDData` or None, optional A dark frame to be subtracted from the ccd. + Default is ``None``. - master_flat: None or `~ccdproc.CCDData` + master_flat : `~ccdproc.CCDData` or None, optional A master flat frame to be divided into ccd. + Default is ``None``. - bad_pixel_mask: None or `~numpy.ndarray` + bad_pixel_mask : `numpy.ndarray` or None, optional 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. + Default is ``None``. - gain: None or `~astropy.Quantity` - Gain value to multiple the image by to convert to electrons + gain : `~astropy.units.Quantity` or None, optional + Gain value to multiple the image by to convert to electrons. + Default is ``None``. - readnoise: None or `~astropy.Quantity` - Read noise for the observations. The read noise should be in + readnoise : `~astropy.units.Quantity` or None, optional + Read noise for the observations. The read noise should be in electrons. + Default is ``None``. - oscan_median : bool, optional - If true, takes the median of each line. Otherwise, uses the mean + oscan_median : bool, optional + If true, takes the median of each line. Otherwise, uses the mean. + Default is ``True``. - oscan_model : `~astropy.modeling.Model`, optional - Model to fit to the data. If None, returns the values calculated + oscan_model : `~astropy.modeling.Model` or None, optional + Model to fit to the data. If None, returns the values calculated by the median or the mean. + Default is ``None``. - min_value : None or float - Minimum value for flat field. The value can either be None and no + min_value : float or None, optional + Minimum value for flat field. The value can either be None and no minimum value is applied to the flat or specified by a float which will replace all values in the flat by the min_value. + Default is ``None``. - dark_exposure : `~astropy.units.Quantity` + dark_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the dark image; if specified, must also provided ``data_exposure``. + Default is ``None``. - data_exposure : `~astropy.units.Quantity` + data_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the science image; if specified, must also provided ``dark_exposure``. + Default is ``None``. - exposure_key : str or `~ccdproc.Keyword` + exposure_key : `~ccdproc.Keyword`, str or None, optional Name of key in image metadata that contains exposure time. + Default is ``None``. - exposure_unit : `~astropy.units.Unit` + exposure_unit : `~astropy.units.Unit` or None, optional Unit of the exposure time if the value in the meta data does not include a unit. + Default is ``None``. - dark_scale: boolean - If True, scale the dark frame by the exposure times + dark_scale : bool, optional + If True, scale the dark frame by the exposure times. + Default is ``False``. Returns ------- - occd: `~ccdproc.CCDData` - Reduded ccd + occd : `~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 ccdproc import CCDData - >>> from ccdproc 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) - - + 1. To overscan, trim and gain correct a data set:: + + >>> import numpy as np + >>> from astropy import units as u + >>> from ccdproc import CCDData + >>> from ccdproc 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() @@ -170,7 +186,7 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, elif oscan is None: pass else: - raise TypeError('oscan is not None, a string, or CCDData object') + raise TypeError('oscan is not None, a string, or CCDData object.') # apply the trim correction if isinstance(trim, six.string_types): @@ -178,14 +194,14 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, elif trim is None: pass else: - raise TypeError('trim is not None or a string') + raise TypeError('trim is not None or a string.') # create the error frame if error and gain is not None and readnoise is not None: nccd = create_deviation(nccd, gain=gain, readnoise=readnoise) elif error and (gain is None or readnoise is None): raise ValueError( - 'gain and readnoise must be specified to create error frame') + 'gain and readnoise must be specified to create error frame.') # apply the bad pixel mask if isinstance(bad_pixel_mask, np.ndarray): @@ -193,15 +209,15 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, elif bad_pixel_mask is None: pass else: - raise TypeError('bad_pixel_mask is not None or numpy.ndarray') + raise TypeError('bad_pixel_mask is not None or numpy.ndarray.') # apply the gain correction - if isinstance(gain, u.quantity.Quantity): + if isinstance(gain, Quantity): nccd = gain_correct(nccd, gain) elif gain is None: pass else: - raise TypeError('gain is not None or astropy.Quantity') + raise TypeError('gain is not None or astropy.units.Quantity.') # subtracting the master bias if isinstance(master_bias, CCDData): @@ -210,20 +226,20 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, pass else: raise TypeError( - 'master_bias is not None, numpy.ndarray, or a CCDData object') + 'master_bias is not None, numpy.ndarray, or a CCDData object.') # subtract the dark frame if isinstance(dark_frame, CCDData): nccd = subtract_dark(nccd, dark_frame, dark_exposure=dark_exposure, - data_exposure=data_exposure, + data_exposure=data_exposure, exposure_time=exposure_key, - exposure_unit=exposure_unit, + exposure_unit=exposure_unit, scale=dark_scale) elif dark_frame is None: pass else: raise TypeError( - 'dark_frame is not None or a CCDData object') + 'dark_frame is not None or a CCDData object.') # test dividing the master flat if isinstance(master_flat, CCDData): @@ -232,7 +248,7 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, pass else: raise TypeError( - 'master_flat is not None, numpy.ndarray, or a CCDData object') + 'master_flat is not None, numpy.ndarray, or a CCDData object.') return nccd @@ -240,25 +256,26 @@ def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, @log_to_metadata def create_deviation(ccd_data, gain=None, readnoise=None): """ - Create a uncertainty frame. The function will update the uncertainty + Create a uncertainty frame. The function will update the uncertainty plane which gives the standard deviation for the data. Gain is used in this function only to scale the data in constructing the deviation; the data is not scaled. Parameters ---------- - ccd_data : `~ccdproc.CCDData` Data whose deviation will be calculated. - gain : `~astropy.units.Quantity`, optional + gain : `~astropy.units.Quantity` or None, optional Gain of the CCD; necessary only if ``ccd_data`` and ``readnoise`` are not in the same units. In that case, the units of ``gain`` should be those that convert ``ccd_data.data`` to the same units as ``readnoise``. + Default is ``None``. - readnoise : `~astropy.units.Quantity` + readnoise : `~astropy.units.Quantity` or None, optional Read noise per pixel. + Default is ``None``. {log} @@ -270,25 +287,25 @@ def create_deviation(ccd_data, gain=None, readnoise=None): Returns ------- - ccd : `~ccdproc.CCDData` + ccd : `~ccdproc.CCDData` CCDData object with uncertainty created; uncertainty is in the same units as the data in the parameter ``ccd_data``. """ if gain is not None and not isinstance(gain, Quantity): - raise TypeError('gain must be a astropy.units.Quantity') + raise TypeError('gain must be a astropy.units.Quantity.') if readnoise is None: - raise ValueError('Must provide a readnoise.') + raise ValueError('must provide a readnoise.') if not isinstance(readnoise, Quantity): - raise TypeError('readnoise must be a astropy.units.Quantity') + raise TypeError('readnoise must be a astropy.units.Quantity.') if gain is None: gain = 1.0 * u.dimensionless_unscaled if gain.unit * ccd_data.unit != readnoise.unit: - raise u.UnitsError("Units of data, gain and readnoise do not match") + raise u.UnitsError("units of data, gain and readnoise do not match.") # Need to convert Quantity to plain number because NDData data is not # a Quantity. All unit checking should happen prior to this point. @@ -312,13 +329,14 @@ def subtract_overscan(ccd, overscan=None, overscan_axis=1, fits_section=None, Parameters ---------- ccd : `~ccdproc.CCDData` - Data to have overscan frame corrected + Data to have overscan frame corrected. - overscan : `~ccdproc.CCDData` + overscan : `~ccdproc.CCDData` or None, optional Slice from ``ccd`` that contains the overscan. Must provide either this argument or ``fits_section``, but not both. + Default is ``None``. - overscan_axis : None, 0 or 1, optional + overscan_axis : 0, 1 or None, optional Axis along which overscan should combined with mean or median. Axis numbering follows the *python* convention for ordering, so 0 is the first axis and 1 is the second axis. @@ -326,18 +344,22 @@ def subtract_overscan(ccd, overscan=None, overscan_axis=1, fits_section=None, If overscan_axis is explicitly set to None, the axis is set to the shortest dimension of the overscan section (or 1 in case of a square overscan). + Default is ``1``. - fits_section : str + fits_section : str or None, optional Region of ``ccd`` from which the overscan is extracted, using the FITS conventions for index order and index start. See Notes and Examples below. Must provide either this argument or ``overscan``, but not both. + Default is ``None``. - median : bool, optional - If true, takes the median of each line. Otherwise, uses the mean + median : bool, optional + If true, takes the median of each line. Otherwise, uses the mean. + Default is ``False``. - model : `~astropy.modeling.Model`, optional - Model to fit to the data. If None, returns the values calculated + model : `~astropy.modeling.Model` or None, optional + Model to fit to the data. If None, returns the values calculated by the median or the mean. + Default is ``None``. {log} @@ -349,13 +371,11 @@ def subtract_overscan(ccd, overscan=None, overscan_axis=1, fits_section=None, Returns ------- - ccd : `~ccdproc.CCDData` - CCDData object with overscan subtracted - + ccd : `~ccdproc.CCDData` + CCDData object with overscan subtracted. Notes ----- - The format of the ``fits_section`` string follow the rules for slices that are consistent with the FITS standard (v3) and IRAF usage of keywords like TRIMSEC and BIASSEC. Its indexes are one-based, instead of the @@ -370,37 +390,40 @@ def subtract_overscan(ccd, overscan=None, overscan_axis=1, fits_section=None, Examples -------- + Creating a 100x100 array containing ones just for demonstration purposes:: - >>> import numpy as np - >>> from astropy import units as u - >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) + >>> import numpy as np + >>> from astropy import units as u + >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) The statement below uses all rows of columns 90 through 99 as the - overscan. + overscan:: - >>> no_scan = subtract_overscan(arr1, overscan=arr1[:, 90:100]) - >>> assert (no_scan.data == 0).all() + >>> no_scan = subtract_overscan(arr1, overscan=arr1[:, 90:100]) + >>> assert (no_scan.data == 0).all() - This statement does the same as the above, but with a FITS-style section. + This statement does the same as the above, but with a FITS-style section:: - >>> no_scan = subtract_overscan(arr1, fits_section='[91:100, :]') - >>> assert (no_scan.data == 0).all() + >>> no_scan = subtract_overscan(arr1, fits_section='[91:100, :]') + >>> assert (no_scan.data == 0).all() Spaces are stripped out of the ``fits_section`` string. """ if not (isinstance(ccd, CCDData) or isinstance(ccd, np.ndarray)): - raise TypeError('ccddata is not a CCDData or ndarray object') + raise TypeError('ccddata is not a CCDData or ndarray object.') if ((overscan is not None and fits_section is not None) or (overscan is None and fits_section is None)): - raise TypeError('Specify either overscan or fits_section, but not both') + raise TypeError('specify either overscan or fits_section, but not ' + 'both.') if (overscan is not None) and (not isinstance(overscan, CCDData)): - raise TypeError('overscan is not a CCDData object') + raise TypeError('overscan is not a CCDData object.') - if (fits_section is not None) and not isinstance(fits_section, six.string_types): - raise TypeError('overscan is not a string') + if (fits_section is not None and + not isinstance(fits_section, six.string_types)): + raise TypeError('overscan is not a string.') if fits_section is not None: overscan = ccd[slice_from_string(fits_section, fits_convention=True)] @@ -442,54 +465,53 @@ def trim_image(ccd, fits_section=None): Parameters ---------- - ccd : `~ccdproc.CCDData` CCD image to be trimmed, sliced if desired. - fits_section : str + fits_section : str or None, optional Region of ``ccd`` from which the overscan is extracted; see `~ccdproc.subtract_overscan` for details. + Default is ``None``. {log} Returns ------- - trimmed_ccd : `~ccdproc.CCDData` Trimmed image. Examples -------- - Given an array that is 100x100, - >>> import numpy as np - >>> from astropy import units as u - >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) + >>> import numpy as np + >>> from astropy import units as u + >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) the syntax for trimming this to keep all of the first index but only the first 90 rows of the second index is - >>> trimmed = trim_image(arr1[:, :90]) - >>> trimmed.shape - (100, 90) - >>> trimmed.data[0, 0] = 2 - >>> arr1.data[0, 0] - 1.0 + >>> trimmed = trim_image(arr1[:, :90]) + >>> trimmed.shape + (100, 90) + >>> trimmed.data[0, 0] = 2 + >>> arr1.data[0, 0] + 1.0 This both trims *and makes a copy* of the image. Indexing the image directly does *not* do the same thing, quite: - >>> not_really_trimmed = arr1[:, :90] - >>> not_really_trimmed.data[0, 0] = 2 - >>> arr1.data[0, 0] - 2.0 + >>> not_really_trimmed = arr1[:, :90] + >>> not_really_trimmed.data[0, 0] = 2 + >>> arr1.data[0, 0] + 2.0 In this case, ``not_really_trimmed`` is a view of the underlying array ``arr1``, not a copy. """ - if fits_section is not None and not isinstance(fits_section, six.string_types): + if (fits_section is not None and + not isinstance(fits_section, six.string_types)): raise TypeError("fits_section must be a string.") trimmed = ccd.copy() if fits_section: @@ -506,20 +528,18 @@ def subtract_bias(ccd, master): Parameters ---------- - ccd : `~ccdproc.CCDData` - Image from which bias will be subtracted + Image from which bias will be subtracted. master : `~ccdproc.CCDData` - Master image to be subtracted from ``ccd`` + Master image to be subtracted from ``ccd``. {log} Returns ------- - - result : `~ccdproc.CCDData` - CCDData object with bias subtracted + result : `~ccdproc.CCDData` + CCDData object with bias subtracted. """ result = ccd.subtract(master) result.meta = ccd.meta.copy() @@ -535,51 +555,54 @@ def subtract_dark(ccd, master, dark_exposure=None, data_exposure=None, Parameters ---------- - ccd : `~ccdproc.CCDData` - Image from which dark will be subtracted + Image from which dark will be subtracted. master : `~ccdproc.CCDData` - Dark image + Dark image. - dark_exposure : `~astropy.units.Quantity` + dark_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the dark image; if specified, must also provided ``data_exposure``. + Default is ``None``. - data_exposure : `~astropy.units.Quantity` + data_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the science image; if specified, must also provided ``dark_exposure``. + Default is ``None``. - exposure_time : str or `~ccdproc.Keyword` + exposure_time : str or `~ccdproc.Keyword` or None, optional Name of key in image metadata that contains exposure time. + Default is ``None``. - exposure_unit : `~astropy.units.Unit` + exposure_unit : `~astropy.units.Unit` or None, optional Unit of the exposure time if the value in the meta data does not include a unit. + Default is ``None``. - scale: boolean - If True, scale the dark frame by the exposure times + scale: bool, optional + If True, scale the dark frame by the exposure times. + Default is ``False``. {log} Returns ------- - result : `~ccdproc.CCDData` - Dark-subtracted image + Dark-subtracted image. """ if not (isinstance(ccd, CCDData) and isinstance(master, CCDData)): - raise TypeError("ccd and master must both be CCDData objects") + raise TypeError("ccd and master must both be CCDData objects.") if (data_exposure is not None and dark_exposure is not None and exposure_time is not None): - raise TypeError("Specify either exposure_time or " + raise TypeError("specify either exposure_time or " "(dark_exposure and data_exposure), not both.") if data_exposure is None and dark_exposure is None: if exposure_time is None: - raise TypeError("Must specify either exposure_time or both " + raise TypeError("must specify either exposure_time or both " "dark_exposure and data_exposure.") if isinstance(exposure_time, Keyword): data_exposure = exposure_time.value_from(ccd.header) @@ -595,10 +618,10 @@ def subtract_dark(ccd, master, dark_exposure=None, data_exposure=None, data_exposure *= exposure_unit dark_exposure *= exposure_unit except TypeError: - raise TypeError("Must provide unit for exposure time") + raise TypeError("must provide unit for exposure time.") else: raise TypeError("exposure times must be astropy.units.Quantity " - "objects") + "objects.") if scale: master_scaled = master.copy() @@ -620,21 +643,22 @@ def gain_correct(ccd, gain, gain_unit=None): Parameters ---------- ccd : `~ccdproc.CCDData` - Data to have gain corrected + Data to have gain corrected. - gain : `~astropy.units.Quantity` or `~ccdproc.Keyword` - gain value for the image expressed in electrons per adu + gain : `~astropy.units.Quantity` or `~ccdproc.Keyword` + gain value for the image expressed in electrons per adu. - gain_unit : `~astropy.units.Unit`, optional + gain_unit : `~astropy.units.Unit` or None, optional Unit for the ``gain``; used only if ``gain`` itself does not provide units. + Default is ``None``. {log} Returns ------- - result : `~ccdproc.CCDData` - CCDData object with gain corrected + result : `~ccdproc.CCDData` + CCDData object with gain corrected. """ if isinstance(gain, Keyword): gain_value = gain.value_from(ccd.header) @@ -656,22 +680,23 @@ def flat_correct(ccd, flat, min_value=None): Parameters ---------- ccd : `~ccdproc.CCDData` - Data to be flatfield corrected + Data to be flatfield corrected. flat : `~ccdproc.CCDData` - Flatfield to apply to the data + Flatfield to apply to the data. - min_value : None or float - Minimum value for flat field. The value can either be None and no + min_value : float or None, optional + Minimum value for flat field. The value can either be None and no minimum value is applied to the flat or specified by a float which will replace all values in the flat by the min_value. + Default is ``None``. {log} Returns ------- - ccd : `~ccdproc.CCDData` - CCDData object with flat corrected + ccd : `~ccdproc.CCDData` + CCDData object with flat corrected. """ # Use the min_value to replace any values in the flat use_flat = flat @@ -692,7 +717,7 @@ def flat_correct(ccd, flat, min_value=None): @log_to_metadata def transform_image(ccd, transform_func, **kwargs): - """Transform the image + """Transform the image. Using the function specified by transform_func, the transform will be applied to data, uncertainty, and mask in ccd. @@ -700,55 +725,52 @@ def transform_image(ccd, transform_func, **kwargs): Parameters ---------- ccd : `~ccdproc.CCDData` - Data to be flatfield corrected + Data to be flatfield corrected. transform_func : function - Function to be used to transform the data + Function to be used to transform the data. - kwargs: dict - Dictionary of arguments to be used by the transform_func. + kwargs : + Additional keyword arguments to be used by the transform_func. {log} Returns ------- - ccd : `~ccdproc.CCDData` - A transformed CCDData object + ccd : `~ccdproc.CCDData` + A transformed CCDData object. Notes ----- - At this time, transform will be applied to the uncertainy data but it - will only transform the data. This will not properly handle uncertainties + will only transform the data. This will not properly handle uncertainties that arise due to correlation between the pixels. - These should only be geometric transformations of the images. Other + These should only be geometric transformations of the images. Other methods should be used if the units of ccd need to be changed. Examples -------- + Given an array that is 100x100:: - Given an array that is 100x100, + >>> import numpy as np + >>> from astropy import units as u + >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) - >>> import numpy as np - >>> from astropy import units as u - >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) - - the syntax for transforming the array using - scipy.ndimage.interpolation.shift - - >>> from scipy.ndimage.interpolation import shift - >>> from ccdproc import transform_image - >>> transformed = transform_image(arr1, shift, shift=(5.5, 8.1)) + The syntax for transforming the array using + `scipy.ndimage.shift`:: + >>> from scipy.ndimage.interpolation import shift + >>> from ccdproc import transform_image + >>> transformed = transform_image(arr1, shift, shift=(5.5, 8.1)) """ # check that it is a ccddata object if not (isinstance(ccd, CCDData)): - raise TypeError('ccd is not a CCDData') + raise TypeError('ccd is not a CCDData.') # check that transform is a callable function if not hasattr(transform_func, '__call__'): - raise TypeError('transform is not a function') + raise TypeError('transform is not a function.') # make a copy of the object nccd = ccd.copy() @@ -783,31 +805,35 @@ def wcs_project(ccd, target_wcs, target_shape=None, order='bilinear'): ccd : `~ccdproc.CCDData` Data to be projected. - target_wcs : `astropy.wcs.WCS` object + target_wcs : `~astropy.wcs.WCS` object WCS onto which all images should be projected. - target_shape : two element list-like, optional + target_shape : two element list-like or None, optional Shape of the output image. If omitted, defaults to the shape of the input image. + Default is ``None``. order : str, optional Interpolation order for re-projection. Must be one of: + + 'nearest-neighbor' + 'bilinear' + 'biquadratic' + 'bicubic' + Default is ``'bilinear'``. + {log} Returns ------- ccd : `~ccdproc.CCDData` - A transformed CCDData object + A transformed CCDData object. """ from reproject import reproject_interp if not (ccd.wcs.is_celestial and target_wcs.is_celestial): - raise ValueError('One or both WCS is not celestial.') + raise ValueError('one or both WCS is not celestial.') if target_shape is None: target_shape = ccd.shape @@ -857,58 +883,60 @@ def sigma_func(arr, axis=None): Parameters ---------- - arr : `~ccdproc.CCDData` or `~numpy.ndarray` + arr : `~ccdproc.CCDData` or `numpy.ndarray` Array whose deviation is to be calculated. - axis : None or int or tuple of ints, optional + axis : int, tuple of ints or None, optional Axis or axes along which the function is performed. - If ``None`` (the default) it is performed over all the dimensions of + If ``None`` it is performed over all the dimensions of the input array. The axis argument can also be negative, in this case it counts from the last to the first axis. + Default is ``None``. Returns ------- - float + uncertainty : float uncertainty of array estimated from median absolute deviation. """ return stats.median_absolute_deviation(arr) * 1.482602218505602 def setbox(x, y, mbox, xmax, ymax): - """Create a box of length mbox around a position x,y. If the box will - be out of [0,len] then reset the edges of the box to be within the - boundaries + """ + Create a box of length mbox around a position x,y. If the box will + be out of [0,len] then reset the edges of the box to be within the + boundaries. - Parameters - ---------- - x : int - Central x-position of box + Parameters + ---------- + x : int + Central x-position of box. - y : int - Central y-position of box + y : int + Central y-position of box. - mbox : int - Width of box + mbox : int + Width of box. - xmax : int - Maximum x value + xmax : int + Maximum x value. - ymax : int - Maximum y value + ymax : int + Maximum y value. - Returns - ------- - x1 : int - Lower x corner of box + Returns + ------- + x1 : int + Lower x corner of box. - x2 : int - Upper x corner of box + x2 : int + Upper x corner of box. - y1 : int - Lower y corner of box + y1 : int + Lower y corner of box. - y2 : int - Upper y corner of box + y2 : int + Upper y corner of box. """ mbox = max(int(0.5 * mbox), 1) y1 = max(0, y - mbox) @@ -928,29 +956,26 @@ def background_deviation_box(data, bbox): Parameters ---------- + data : `numpy.ndarray` or `numpy.ma.MaskedArray` + Data to measure background deviation. - data : `~numpy.ndarray` or `~numpy.ma.MaskedArray` - Data to measure background deviation - - bbox : int - Box size for calculating background deviation + bbox : int + Box size for calculating background deviation. Raises ------ - ValueError - A value error is raised if bbox is less than 1 + A value error is raised if bbox is less than 1. Returns ------- - background : `~numpy.ndarray` or `~numpy.ma.MaskedArray` - An array with the measured background deviation in each pixel - + background : `numpy.ndarray` or `numpy.ma.MaskedArray` + An array with the measured background deviation in each pixel. """ # Check to make sure the background box is an appropriate size # If it is too small, then insufficient statistics are generated if bbox < 1: - raise ValueError('bbox must be greater than 1') + raise ValueError('bbox must be greater than 1.') # make the background image barr = data * 0.0 + data.std() @@ -970,26 +995,25 @@ def background_deviation_filter(data, bbox): Parameters ---------- - data : `~numpy.ndarray` - Data to measure background deviation + data : `numpy.ndarray` + Data to measure background deviation. - bbox : int - Box size for calculating background deviation + bbox : int + Box size for calculating background deviation. Raises ------ ValueError - A value error is raised if bbox is less than 1 + A value error is raised if bbox is less than 1. Returns ------- - background : `~numpy.ndarray` or `~numpy.ma.MaskedArray` - An array with the measured background deviation in each pixel - + background : `numpy.ndarray` or `numpy.ma.MaskedArray` + An array with the measured background deviation in each pixel. """ # Check to make sure the background box is an appropriate size if bbox < 1: - raise ValueError('bbox must be greater than 1') + raise ValueError('bbox must be greater than 1.') return ndimage.generic_filter(data, sigma_func, size=(bbox, bbox)) @@ -1000,27 +1024,27 @@ def rebin(ccd, newshape): Parameters ---------- - data : `~ccdproc.CCDData` or `~numpy.ndarray` - Data to rebin + data : `~ccdproc.CCDData` or `numpy.ndarray` + Data to rebin. newshape : tuple - Tuple containing the new shape for the array + Tuple containing the new shape for the array. Returns ------- - output : `~ccdproc.CCDData` or `~numpy.ndarray` - An array with the new shape. It will have the same type as the input + output : `~ccdproc.CCDData` or `numpy.ndarray` + An array with the new shape. It will have the same type as the input object. Raises ------ TypeError - A type error is raised if data is not an `~numpy.ndarray` or - `~ccdproc.CCDData` + A type error is raised if data is not an `numpy.ndarray` or + `~ccdproc.CCDData`. ValueError A value error is raised if the dimenisions of new shape is not equal - to data + to data. Notes ----- @@ -1032,24 +1056,24 @@ def rebin(ccd, newshape): Examples -------- - Given an array that is 100x100, - - >>> import numpy as np - >>> from astropy import units as u - >>> arr1 = CCDData(np.ones([10, 10]), unit=u.adu) + Given an array that is 100x100:: - the syntax for rebinning an array to a shape - of (20,20) is + >>> import numpy as np + >>> from astropy import units as u + >>> arr1 = CCDData(np.ones([10, 10]), unit=u.adu) - >>> rebinned = rebin(arr1, (20,20)) + The syntax for rebinning an array to a shape + of (20,20) is:: + >>> rebinned = rebin(arr1, (20,20)) """ # check to see that is in a nddata type if isinstance(ccd, np.ndarray): # check to see that the two arrays are going to be the same length if len(ccd.shape) != len(newshape): - raise ValueError('newshape does not have the same dimensions as ccd') + raise ValueError('newshape does not have the same dimensions as ' + 'ccd.') slices = [slice(0, old, old/new) for old, new in zip(ccd.shape, newshape)] @@ -1060,7 +1084,8 @@ def rebin(ccd, newshape): elif isinstance(ccd, CCDData): # check to see that the two arrays are going to be the same length if len(ccd.shape) != len(newshape): - raise ValueError('newshape does not have the same dimensions as ccd') + raise ValueError('newshape does not have the same dimensions as ' + 'ccd.') nccd = ccd.copy() # rebin the data plane @@ -1076,34 +1101,34 @@ def rebin(ccd, newshape): return nccd else: - raise TypeError('ccd is not an ndarray or a CCDData object') + raise TypeError('ccd is not an ndarray or a CCDData object.') def _blkavg(data, newshape): """ - Block average an array such that it has the new shape + Block average an array such that it has the new shape. Parameters ---------- - data : `~numpy.ndarray` or `~numpy.ma.MaskedArray` - Data to average + data : `numpy.ndarray` or `numpy.ma.MaskedArray` + Data to average. newshape : tuple - Tuple containing the new shape for the array + Tuple containing the new shape for the array. Returns ------- - output : `~numpy.ndarray` or `~numpy.ma.MaskedArray` - An array with the new shape and the average of the pixels + output : `numpy.ndarray` or `numpy.ma.MaskedArray` + An array with the new shape and the average of the pixels. Raises ------ TypeError - A type error is raised if data is not an `numpy.ndarray` + A type error is raised if data is not an `numpy.ndarray`. ValueError A value error is raised if the dimensions of new shape is not equal - to data + to data. Notes ----- @@ -1112,11 +1137,11 @@ def _blkavg(data, newshape): """ # check to see that is in a nddata type if not isinstance(data, np.ndarray): - raise TypeError('data is not a ndarray object') + raise TypeError('data is not a ndarray object.') # check to see that the two arrays are going to be the same length if len(data.shape) != len(newshape): - raise ValueError('newshape does not have the same dimensions as data') + raise ValueError('newshape does not have the same dimensions as data.') shape = data.shape lenShape = len(shape) @@ -1130,89 +1155,113 @@ def _blkavg(data, newshape): def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, - objlim=5.0, gain=1.0, readnoise=6.5, - satlevel=65536.0, pssl=0.0, niter=4, + objlim=5.0, gain=1.0, readnoise=6.5, + satlevel=65536.0, pssl=0.0, niter=4, sepmed=True, cleantype='meanmask', fsmode='median', - psfmodel='gauss', psffwhm=2.5, psfsize=7, - psfk=None, psfbeta=4.765, verbose=False): + psfmodel='gauss', psffwhm=2.5, psfsize=7, + psfk=None, psfbeta=4.765, verbose=False): r""" Identify cosmic rays through the lacosmic technique. The lacosmic technique identifies cosmic rays by identifying pixels based on a variation of the - Laplacian edge detection. The algorithm is an implementation of the - code describe in van Dokkum (2001) :ref:[1]_ as implemented by McCully (2014) - [2]_. If you use this algorithm, please cite these two works. + Laplacian edge detection. The algorithm is an implementation of the + code describe in van Dokkum (2001) [1]_ as implemented by McCully (2014) + [2]_. If you use this algorithm, please cite these two works. Parameters ---------- - ccd: `~ccdproc.CCDData` or `~numpy.ndarray` - Data to have cosmic ray cleaned + ccd : `~ccdproc.CCDData` or `numpy.ndarray` + Data to have cosmic ray cleaned. + sigclip : float, optional Laplacian-to-noise limit for cosmic ray detection. Lower values will flag more pixels as cosmic rays. Default: 4.5. + sigfrac : float, optional Fractional detection limit for neighboring pixels. For cosmic ray neighbor pixels, a lapacian-to-noise detection limit of sigfrac * sigclip will be used. Default: 0.3. + objlim : float, optional Minimum contrast between Laplacian image and the fine structure image. Increase this value if cores of bright stars are flagged as cosmic rays. Default: 5.0. + pssl : float, optional Previously subtracted sky level in ADU. We always need to work in electrons for cosmic ray detection, so we need to know the sky level that has been subtracted so we can add it back in. Default: 0.0. + gain : float, optional Gain of the image (electrons / ADU). We always need to work in electrons for cosmic ray detection. Default: 1.0 + readnoise : float, optional Read noise of the image (electrons). Used to generate the noise model of the image. Default: 6.5. + satlevel : float, optional Saturation of level of the image (electrons). This value is used to detect saturated stars and pixels at or above this level are added to the mask. Default: 65536.0. + niter : int, optional Number of iterations of the LA Cosmic algorithm to perform. Default: 4. - sepmed : boolean, optional + + sepmed : bool, optional Use the separable median filter instead of the full median filter. The separable median is not identical to the full median filter, but they are approximately the same and the separable median filter is significantly faster and still detects cosmic rays well. Default: True - cleantype : {'median', 'medmask', 'meanmask', 'idw'}, optional - Set which clean algorithm is used:\n - 'median': An umasked 5x5 median filter\n - 'medmask': A masked 5x5 median filter\n - 'meanmask': A masked 5x5 mean filter\n - 'idw': A masked 5x5 inverse distance weighted interpolation\n - Default: "meanmask". - fsmode : {'median', 'convolve'}, optional - Method to build the fine structure image:\n - 'median': Use the median filter in the standard LA Cosmic algorithm - 'convolve': Convolve the image with the psf kernel to calculate the - fine structure image. - Default: 'median'. - psfmodel : {'gauss', 'gaussx', 'gaussy', 'moffat'}, optional + + cleantype : str, optional + Set which clean algorithm is used: + + - ``"median"``: An umasked 5x5 median filter. + - ``"medmask"``: A masked 5x5 median filter. + - ``"meanmask"``: A masked 5x5 mean filter. + - ``"idw"``: A masked 5x5 inverse distance weighted interpolation. + + Default: ``"meanmask"``. + + fsmode : str, optional + Method to build the fine structure image: + + - ``"median"``: Use the median filter in the standard LA Cosmic \ + algorithm. + - ``"convolve"``: Convolve the image with the psf kernel to calculate \ + the fine structure image. + + Default: ``"median"``. + + psfmodel : str, optional Model to use to generate the psf kernel if fsmode == 'convolve' and - psfk is None. The current choices are Gaussian and Moffat profiles. - 'gauss' and 'moffat' produce circular PSF kernels. The 'gaussx' and - 'gaussy' produce Gaussian kernels in the x and y directions - respectively. Default: "gauss". + psfk is None. The current choices are Gaussian and Moffat profiles: + + - ``"gauss"`` and ``"moffat"`` produce circular PSF kernels. + - The ``"gaussx"`` and ``"gaussy"`` produce Gaussian kernels in the x \ + and y directions respectively. + + Default: ``"gauss"``. + psffwhm : float, optional Full Width Half Maximum of the PSF to use to generate the kernel. Default: 2.5. + psfsize : int, optional Size of the kernel to calculate. Returned kernel will have size psfsize x psfsize. psfsize should be odd. Default: 7. - psfk : float numpy array, optional + + psfk : `numpy.ndarray` (with float dtype) or None, optional PSF kernel array to use for the fine structure image if - fsmode == 'convolve'. If None and fsmode == 'convolve', we calculate - the psf kernel using 'psfmodel'. Default: None. + ``fsmode == 'convolve'``. If None and ``fsmode == 'convolve'``, we + calculate the psf kernel using ``psfmodel``. Default: None. + psfbeta : float, optional - Moffat beta parameter. Only used if fsmode=='convolve' and - psfmodel=='moffat'. Default: 4.765. - verbose : boolean, optional + Moffat beta parameter. Only used if ``fsmode=='convolve'`` and + ``psfmodel=='moffat'``. Default: 4.765. + + verbose : bool, optional Print to the screen or not. Default: False. - {log} Notes ----- @@ -1221,12 +1270,13 @@ def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, Returns ------- - nccd : `~ccdproc.CCDData` or `~numpy.ndarray` - An object of the same type as ccd is returned. If it is a + nccd : `~ccdproc.CCDData` or `numpy.ndarray` + An object of the same type as ccd is returned. If it is a `~ccdproc.CCDData`, the mask attribute will also be updated with areas identified with cosmic rays masked. - crmask : `~numpy.ndarray` - If an `~numpy.ndarray` is provided as ccd, a boolean ndarray with the + + crmask : `numpy.ndarray` + If an `numpy.ndarray` is provided as ccd, a boolean ndarray with the cosmic rays identified will also be returned. References @@ -1247,7 +1297,7 @@ def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, >>> newdata, mask = cosmicray_lacosmic(data, sigclip=5) #doctest: +SKIP where the error is an array that is the same shape as data but - includes the pixel error. This would return a data array, newdata, + includes the pixel error. This would return a data array, newdata, with the bad pixels replaced by the local median from a box of 11 pixels; and it would return a mask indicating the bad pixels. @@ -1265,13 +1315,13 @@ def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, data = ccd crmask, cleanarr = detect_cosmics( - data, inmask=None, sigclip=sigclip, - sigfrac=sigfrac, objlim=objlim, gain=gain, - readnoise=readnoise, satlevel=satlevel, pssl=pssl, - niter=niter, sepmed=sepmed, cleantype=cleantype, - fsmode=fsmode, psfmodel=psfmodel, psffwhm=psffwhm, - psfsize=psfsize, psfk=psfk, psfbeta=psfbeta, - verbose=verbose) + data, inmask=None, sigclip=sigclip, + sigfrac=sigfrac, objlim=objlim, gain=gain, + readnoise=readnoise, satlevel=satlevel, pssl=pssl, + niter=niter, sepmed=sepmed, cleantype=cleantype, + fsmode=fsmode, psfmodel=psfmodel, psffwhm=psffwhm, + psfsize=psfsize, psfk=psfk, psfbeta=psfbeta, + verbose=verbose) return cleanarr, crmask @@ -1280,8 +1330,8 @@ def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, crmask, cleanarr = detect_cosmics( ccd.data, inmask=ccd.mask, sigclip=sigclip, sigfrac=sigfrac, objlim=objlim, gain=gain, - readnoise=readnoise, satlevel=satlevel, pssl=pssl, - niter=niter, sepmed=sepmed, cleantype=cleantype, + readnoise=readnoise, satlevel=satlevel, pssl=pssl, + niter=niter, sepmed=sepmed, cleantype=cleantype, fsmode=fsmode, psfmodel=psfmodel, psffwhm=psffwhm, psfsize=psfsize, psfk=psfk, psfbeta=psfbeta, verbose=verbose) @@ -1296,60 +1346,61 @@ def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, return nccd else: - raise TypeError('ccddata is not a CCDData or ndarray object') + raise TypeError('ccd is not a CCDData or ndarray object.') def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, rbox=0): """ - Identify cosmic rays through median technique. The median technique + Identify cosmic rays through median technique. The median technique identifies cosmic rays by identifying pixels by subtracting a median image from the initial data array. Parameters ---------- + ccd : `~ccdproc.CCDData`, `numpy.ndarray` or `numpy.ma.MaskedArray` + Data to have cosmic ray cleaned. - ccd : `~ccdproc.CCDData` or numpy.ndarray or numpy.MaskedArary - Data to have cosmic ray cleaned - - thresh : float - Threshold for detecting cosmic rays + thresh : float, optional + Threshold for detecting cosmic rays. + Default is ``5``. - error_image : None, float, or `~numpy.ndarray` - Error level. If None, the task will use the standard + error_image : `numpy.ndarray`, float or None, optional + Error level. If None, the task will use the standard deviation of the data. If an ndarray, it should have the same shape as data. + Default is ``None``. - mbox : int - Median box for detecting cosmic rays + mbox : int, optional + Median box for detecting cosmic rays. + Default is ``11``. - gbox : int + gbox : int, optional Box size to grow cosmic rays. If zero, no growing will be done. + Default is ``0``. - rbox : int - Median box for calculating replacement values. If zero, no pixels will + rbox : int, optional + Median box for calculating replacement values. If zero, no pixels will be replaced. - - {log} + Default is ``0``. Notes ----- - Similar implementation to crmedian in iraf.imred.crutil.crmedian + Similar implementation to crmedian in iraf.imred.crutil.crmedian. Returns ------- - nccd : `~ccdproc.CCDData` or `~numpy.ndarray` - An object of the same type as ccd is returned. If it is a + nccd : `~ccdproc.CCDData` or `numpy.ndarray` + An object of the same type as ccd is returned. If it is a `~ccdproc.CCDData`, the mask attribute will also be updated with areas identified with cosmic rays masked. - nccd : `~numpy.ndarray` - If an `~numpy.ndarray` is provided as ccd, a boolean ndarray with the + nccd : `numpy.ndarray` + If an `numpy.ndarray` is provided as ccd, a boolean ndarray with the cosmic rays identified will also be returned. Examples -------- - 1) Given an numpy.ndarray object, the syntax for running cosmicray_median would be: @@ -1358,7 +1409,7 @@ def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, ... rbox=11, gbox=5) # doctest: +SKIP where error is an array that is the same shape as data but - includes the pixel error. This would return a data array, newdata, + includes the pixel error. This would return a data array, newdata, with the bad pixels replaced by the local median from a box of 11 pixels; and it would return a mask indicating the bad pixels. @@ -1371,9 +1422,7 @@ def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, The newccd object will have bad pixels in its data array replace and the mask of the object will be created if it did not previously exist or be updated with the detected cosmic rays. - """ - if isinstance(ccd, np.ndarray): data = ccd @@ -1381,7 +1430,7 @@ def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, error_image = data.std() else: if not isinstance(error_image, (float, np.ndarray)): - raise TypeError('error_image is not a float or ndarray') + raise TypeError('error_image is not a float or ndarray.') # create the median image marr = ndimage.median_filter(data, size=(mbox, mbox)) @@ -1414,7 +1463,7 @@ def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, if error_image is None and ccd.uncertainty is not None: error_image = ccd.uncertainty.array if ccd.data.shape != error_image.shape: - raise ValueError('error_image is not the same shape as data') + raise ValueError('error_image is not the same shape as data.') data, crarr = cosmicray_median(ccd.data, error_image=error_image, thresh=thresh, mbox=mbox, gbox=gbox, @@ -1430,7 +1479,7 @@ def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, return nccd else: - raise TypeError('ccd is not an ndarray or a CCDData object') + raise TypeError('ccd is not an numpy.ndarray or a CCDData object.') class Keyword(object): @@ -1462,27 +1511,25 @@ def value(self, value): self._value = value elif isinstance(value, six.string_types): if self.unit is not None: - raise ValueError("Keyword with a unit cannot have a " + raise ValueError("keyword with a unit cannot have a " "string value.") else: self._value = value else: if self.unit is None: - raise ValueError("No unit provided. Set value with " - "an astropy.units.Quantity") + raise ValueError("no unit provided. Set value with " + "an astropy.units.Quantity.") self._value = value * self.unit def value_from(self, header): """ - Set value of keyword from FITS header + Set value of keyword from FITS header. Parameters ---------- - header : `~astropy.io.fits.Header` - FITS header containing a value for this keyword + FITS header containing a value for this keyword. """ - value_from_header = header[self.name] self.value = value_from_header return self.value diff --git a/ccdproc/image_collection.py b/ccdproc/image_collection.py index c916c1cf..5d4a928c 100644 --- a/ccdproc/image_collection.py +++ b/ccdproc/image_collection.py @@ -22,7 +22,6 @@ class ImageFileCollection(object): - """ Representation of a collection of image files. @@ -34,34 +33,38 @@ class ImageFileCollection(object): Parameters ---------- - location : str, optional - path to directory containing FITS files - keywords : list of str or '*', optional + location : str or None, optional + path to directory containing FITS files. + Default is ``None``. + + keywords : list of str, '*' or None, optional Keywords that should be used as column headings in the summary table. If the value is or includes '*' then all keywords that appear in any of the FITS headers of the files in the collection become table columns. Default value is '*' unless ``info_file`` is specified. - info_file : str, optional + Default is ``None``. + + info_file : str or None, optional Path to file that contains a table of information about FITS files. In this case the keywords are set to the names of the columns of the ``info_file`` unless ``keywords`` is explicitly set to a different list. + Default is ``None``. Raises ------ - ValueError Raised if keywords are set to a combination of '*' and any other value. """ - def __init__(self, location=None, keywords=None, info_file=None): self._location = location self._files = [] if location: self._files = self._fits_files_in_directory() - if self._files==[]: - warnings.warn("No fits files in the collection.", AstropyUserWarning) + if self._files == []: + warnings.warn("no FITS files in the collection.", + AstropyUserWarning) self._summary_info = {} if keywords is None: if info_file is not None: @@ -84,8 +87,8 @@ def __init__(self, location=None, keywords=None, info_file=None): masked=True) except IOError: if location: - logger.warning('Unable to open table file %s, will try ' - 'initializing from location instead', + logger.warning('unable to open table file %s, will try ' + 'initializing from location instead.', info_path) else: raise @@ -102,7 +105,7 @@ def __init__(self, location=None, keywords=None, info_file=None): @property def summary(self): """ - astropy.table.Table of values of FITS keywords for files in the + `~astropy.table.Table` of values of FITS keywords for files in the collection. Each keyword is a column heading. In addition, there is a column @@ -121,14 +124,14 @@ def summary(self): If an explicit list of keywords was supplied in setting up the collection then the order of the columns is the order of the keywords. - """, + """ return self._summary_info @property def summary_info(self): """ - Deprecated -- use summary instead -- astropy.table.Table of values of - FITS keywords for files in the collection. + Deprecated -- use `summary` instead -- `~astropy.table.Table` of values + of FITS keywords for files in the collection. Each keyword is a column heading. In addition, there is a column called 'file' that contains the name of the FITS file. The directory @@ -139,7 +142,7 @@ def summary_info(self): @property def location(self): """ - str, Path name to directory containing FITS files + str, Path name to directory containing FITS files. """ return self._location @@ -169,26 +172,26 @@ def keywords(self, keywords=None): else: self._all_keywords = False - logging.debug('keywords in setter before pruning: %s', keywords) + logging.debug('keywords in setter before pruning: %s.', keywords) # remove duplicates and force a copy new_keys = list(set(keywords)) - logging.debug('keywords after pruning %s', new_keys) + logging.debug('keywords after pruning %s.', new_keys) full_new_keys = list(set(new_keys)) full_new_keys.append('file') full_new_set = set(full_new_keys) current_set = set(self.keywords) if full_new_set.issubset(current_set): - logging.debug('table columns before trimming: %s', + logging.debug('table columns before trimming: %s.', ' '.join(current_set)) cut_keys = current_set.difference(full_new_set) - logging.debug('will try removing columns: %s', + logging.debug('will try removing columns: %s.', ' '.join(cut_keys)) for key in cut_keys: self._summary_info.remove_column(key) - logging.debug('after removal column names are: %s', + logging.debug('after removal column names are: %s.', ' '.join(self.keywords)) else: logging.debug('should be building new table...') @@ -214,6 +217,7 @@ def values(self, keyword, unique=False): unique : bool, optional If True, return only the unique values for the keyword. + Default is ``False``. Returns ------- @@ -238,13 +242,14 @@ def files_filtered(self, **kwd): contains not just the filename, but the full path to each file. The value '*' represents any value. - A missing keyword is indicated by value '' + A missing keyword is indicated by value ''. - Example: - >>> keys = ['imagetyp','filter'] - >>> collection = ImageFileCollection('test/data', keywords=keys) - >>> collection.files_filtered(imagetyp='LIGHT', filter='R') - >>> collection.files_filtered(imagetyp='*', filter='') + Example:: + + >>> keys = ['imagetyp','filter'] + >>> collection = ImageFileCollection('test/data', keywords=keys) + >>> collection.files_filtered(imagetyp='LIGHT', filter='R') + >>> collection.files_filtered(imagetyp='*', filter='') NOTE: Value comparison is case *insensitive* for strings. """ @@ -277,8 +282,9 @@ def sort(self, keys=None): Parameters ---------- - keys : str or list of str + keys : str, list of str or None, optional The key(s) to order the table by. + Default is ``None``. """ if len(self._summary_info) > 0: self._summary_info.sort(keys) @@ -294,17 +300,20 @@ def _dict_from_fits_header(self, file_name, input_summary=None, Parameters ---------- - file_name : str Name of FITS file. - input_summary : dict + input_summary : dict or None, optional Existing dictionary to which new values should be appended. + Default is ``None``. + + missing_marker : any type, optional + Fill value for missing header-keywords. + Default is ``None``. Returns ------- - - file_table : astropy.table.Table + file_table : `~astropy.table.Table` """ from collections import OrderedDict @@ -379,8 +388,7 @@ def _fits_summary(self, header_keywords=None): Parameters ---------- - - header_keywords : list of str or '*', optional + header_keywords : list of str, '*' or None, optional Keywords whose value should be extracted from FITS headers. Default value is ``None``. """ @@ -416,7 +424,7 @@ def _fits_summary(self, header_keywords=None): file_path, input_summary=summary_dict, missing_marker=missing_marker) except IOError as e: - logger.warning('Unable to get FITS header for file %s: %s', + logger.warning('unable to get FITS header for file %s: %s.', file_path, e) continue @@ -457,13 +465,14 @@ def _find_keywords_by_values(self, **kwd): `**kwd` is list of keywords and values the files must have. The value '*' represents any value. - A missing keyword is indicated by value '' + A missing keyword is indicated by value '' + + Example:: - Example: - >>> keys = ['imagetyp','filter'] - >>> collection = ImageFileCollection('test/data', keywords=keys) - >>> collection.files_filtered(imagetyp='LIGHT', filter='R') - >>> collection.files_filtered(imagetyp='*', filter='') + >>> keys = ['imagetyp','filter'] + >>> collection = ImageFileCollection('test/data', keywords=keys) + >>> collection.files_filtered(imagetyp='LIGHT', filter='R') + >>> collection.files_filtered(imagetyp='*', filter='') NOTE: Value comparison is case *insensitive* for strings. """ @@ -479,10 +488,10 @@ def _find_keywords_by_values(self, **kwd): matches = np.array([True] * len(use_info)) for key, value in zip(keywords, values): - logger.debug('Key %s, value %s', key, value) - logger.debug('Value in table %s', use_info[key]) + logger.debug('key %s, value %s', key, value) + logger.debug('value in table %s', use_info[key]) value_missing = use_info[key].mask - logger.debug('Value missing: %s', value_missing) + logger.debug('value missing: %s', value_missing) value_not_missing = np.logical_not(value_missing) if value == '*': have_this_value = value_not_missing @@ -525,13 +534,15 @@ def _fits_files_in_directory(self, extensions=None, Parameters ---------- - extension : list of str, optional + extension : list of str or None, optional List of filename extensions that are FITS files. Default is - ``['fit', 'fits', 'fts']`` + ``['fit', 'fits', 'fts']``. + Default is ``None``. compressed : bool, optional If ``True``, compressed files should be included in the list - (e.g. `.fits.gz`) + (e.g. `.fits.gz`). + Default is ``True``. Returns ------- @@ -568,34 +579,42 @@ def _generator(self, return_type, length, and/or ``overwrite`` is ``True``, a copy of each FITS file will be made. - Parameters ---------- - save_with_name : str + save_with_name : str, optional string added to end of file name (before extension) if FITS file should be saved after iteration. Unless ``save_location`` is set, files will be saved to location of - the source files ``self.location`` + the source files ``self.location``. + Default is ``''``. - save_location : str + save_location : str, optional Directory in which to save FITS files; implies that FITS files will be saved. Note this provides an easy way to copy a directory of files--loop over the {name} with ``save_location`` set. + Default is ``''``. - overwrite : bool + overwrite : bool, optional If ``True``, overwrite input FITS files. + Default is ``False``. + + clobber : bool, optional + Alias for ``overwrite``. + Default is ``False``. - do_not_scale_image_data : bool + do_not_scale_image_data : bool, optional If ``True``, prevents fits from scaling images. Default is ``{default_scaling}``. + Default is ``True``. - return_fname : bool, default is False + return_fname : bool, optional If True, return the tuple (header, file_name) instead of just header. The file name returned is the name of the file only, not the full path to the file. + Default is ``False``. - kwd : dict + kwd : Any additional keywords are used to filter the items returned; see Examples for details. @@ -603,12 +622,11 @@ def _generator(self, return_type, ------- {return_type} If ``return_fname`` is ``False``, yield the next {name} in the - collection + collection. ({return_type}, str) If ``return_fname`` is ``True``, yield a tuple of ({name}, ``file name``) for the next item in the collection. - """ # store mask so we can reset at end--must COPY, otherwise # current_mask just points to the mask of summary_info @@ -638,7 +656,7 @@ def _generator(self, return_type, if (not return_fname) else (return_options[return_type], file_name)) except KeyError: - raise ValueError('No generator for {}'.format(return_type)) + raise ValueError('no generator for {}'.format(return_type)) if save_location: destination_dir = save_location @@ -659,7 +677,7 @@ def _generator(self, return_type, try: hdulist.writeto(new_path, clobber=nuke_existing) except IOError: - logger.error('Error writing file %s', new_path) + logger.error('error writing file %s', new_path) raise hdulist.close() diff --git a/ccdproc/log_meta.py b/ccdproc/log_meta.py index 1da35d33..fb3beb85 100644 --- a/ccdproc/log_meta.py +++ b/ccdproc/log_meta.py @@ -19,9 +19,10 @@ _LOG_ARG_HELP = \ """ {arg} : str, `~ccdproc.Keyword` or dict-like, optional - Item(s) to add to metadata of result. Set to False or None to completely - disable logging. Default is to add a dictionary with a single item: - the key is the name of this function and the value is a string + Item(s) to add to metadata of result. Set to False or None to + completely disable logging. + Default is to add a dictionary with a single item: + The key is the name of this function and the value is a string containing the arguments the function was called with, except the value of this argument. """.format(arg=_LOG_ARGUMENT) @@ -29,7 +30,7 @@ def log_to_metadata(func): """ - Decorator that adds logging to ccdproc functions + Decorator that adds logging to ccdproc functions. The decorator adds the optional argument _LOG_ARGUMENT to function signature and updates the function's docstring to reflect that. @@ -77,7 +78,7 @@ def wrapper(*args, **kwd): # so construct one. key = func.__name__ pos_args = ["{0}={1}".format(arg_name, - _replace_array_with_placeholder(arg_value)) + _replace_array_with_placeholder(arg_value)) for arg_name, arg_value in zip(original_positional_args, args)] kwd_args = ["{0}={1}".format(k, _replace_array_with_placeholder(v)) diff --git a/ccdproc/tests/test_image_collection.py b/ccdproc/tests/test_image_collection.py index 70b4dda7..4597748c 100644 --- a/ccdproc/tests/test_image_collection.py +++ b/ccdproc/tests/test_image_collection.py @@ -451,7 +451,7 @@ def test_initializing_from_table_file_that_does_not_exist(self, info_file='iufadsdhfasdifre') warnings = [rec for rec in caplog.records() if ((rec.levelno == logging.WARN) & - ('Unable to open table file' in rec.message))] + ('unable to open table file' in rec.message))] assert (len(warnings) == 1) # Do we raise an error if the table name is bad AND the location # is None? @@ -466,7 +466,8 @@ def test_initializing_from_table_file_that_does_not_exist(self, def test_no_fits_files_in_collection(self,tmpdir): with catch_warnings(AstropyUserWarning) as warning_lines: - assert("No fits files in the collection.") + # FIXME: What exactly does this assert? + assert("no fits files in the collection.") def test_initialization_with_no_keywords(self, triage_setup): # This test is primarily historical -- the old default for diff --git a/docs/ccdproc/ccddata.rst b/docs/ccdproc/ccddata.rst index 4f00767d..604fbfbc 100644 --- a/docs/ccdproc/ccddata.rst +++ b/docs/ccdproc/ccddata.rst @@ -159,7 +159,7 @@ You can also set the uncertainty directly, either by creating a or by providing a `~numpy.ndarray` with the same shape as the data: >>> ccd.uncertainty = 0.1 * ccd.data # doctest: +ELLIPSIS - INFO: Array provided for uncertainty; assuming it is a StdDevUncertainty. [...] + INFO: array provided for uncertainty; assuming it is a StdDevUncertainty. [...] In this case the uncertainty is assumed to be `~astropy.nddata.StdDevUncertainty`. Using `~astropy.nddata.StdDevUncertainty` @@ -180,7 +180,7 @@ Methods are provided to perform arithmetic operations with a Using these methods propagates errors correctly (if the errors are uncorrelated), take care of any necessary unit conversions, and apply masks -appropriately. Note that the metadata of the result is *not* set if the operation +appropriately. Note that the metadata of the result is *not* set if the operation is between two `~ccdproc.ccddata.CCDData` objects. >>> result = ccd.multiply(0.2 * u.adu) @@ -203,5 +203,5 @@ is between two `~ccdproc.ccddata.CCDData` objects. The arithmetic operators ``*``, ``/``, ``+`` and ``-`` are *not* overridden. .. note:: - If two images have different WCS values, the wcs on the first + If two images have different WCS values, the wcs on the first `~ccdproc.ccddata.CCDData` object will be used for the resultant object.