Example #1
0
class SkyGroup:
    """
    Holds multiple :py:class:`SkyImage` objects whose sky background values
    must be adjusted together.

    `SkyGroup` provides methods for obtaining bounding polygon of the group
    of :py:class:`SkyImage` objects and to compute sky value of the group.

    """
    def __init__(self, images, id=None, sky=0.0):

        if isinstance(images, SkyImage):
            self._images = [images]

        elif hasattr(images, '__iter__'):
            self._images = []
            for im in images:
                if not isinstance(im, SkyImage):
                    raise TypeError("Each element of the 'images' parameter "
                                    "must be an 'SkyImage' object.")
                self._images.append(im)

        else:
            raise TypeError(
                "Parameter 'images' must be either a single "
                "'SkyImage' object or a list of 'SkyImage' objects")

        self._id = id
        self._update_bounding_polygon()
        self._sky = sky
        for im in self._images:
            im.sky += sky

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

            While `id` can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        delta_sky = sky - self._sky
        self._sky = sky
        for im in self._images:
            im.sky += delta_sky

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.

        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def _update_bounding_polygon(self):
        polygons = [im.polygon for im in self._images]
        if len(polygons) == 0:
            self._polygon = SphericalPolygon([])
            self._radec = []
        else:
            self._polygon = SphericalPolygon.multi_union(polygons)
            self._radec = list(self._polygon.to_radec())

    def __len__(self):
        return len(self._images)

    def __getitem__(self, idx):
        return self._images[idx]

    def __setitem__(self, idx, value):
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images[idx] = value
        self._update_bounding_polygon()

    def __delitem__(self, idx):
        del self._images[idx]
        if len(self._images) == 0:
            self._sky = 0.0
            self._id = None
        self._update_bounding_polygon()

    def __iter__(self):
        for image in self._images:
            yield image

    def insert(self, idx, value):
        """Inserts a `SkyImage` into the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.insert(idx, value)
        self._update_bounding_polygon()

    def append(self, value):
        """Appends a `SkyImage` to the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.append(value)
        self._update_bounding_polygon()

    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if len(self._images) == 0:
            return (None, 0, 0.0)

        wght = 0
        area = 0.0

        if overlap is None:
            # compute minimum sky across all images in the group:
            wsky = None

            for image in self._images:
                # make sure all images have the same background:
                image.background = self._sky

                sky, npix, imarea = image.calc_sky(overlap=None, delta=delta)

                if sky is None:
                    continue

                if wsky is None or wsky > sky:
                    wsky = sky
                    wght = npix
                    area = imarea

            return (wsky, wght, area)

        ################################################
        ##  compute weighted sky in various overlaps: ##
        ################################################
        wsky = 0.0

        for image in self._images:
            # make sure all images have the same background:
            image.background = self._sky

            sky, npix, area1 = image.calc_sky(overlap=overlap, delta=delta)

            area += area1

            if sky is not None and npix > 0:
                pix_area = npix * image.pix_area
                wsky += sky * pix_area
                wght += pix_area

        if wght == 0.0 or area == 0.0:
            return (None, wght, area)
        else:
            return (wsky / wght, wght, area)
Example #2
0
class SkyGroup:
    """
    Holds multiple :py:class:`SkyImage` objects whose sky background values
    must be adjusted together.

    `SkyGroup` provides methods for obtaining bounding polygon of the group
    of :py:class:`SkyImage` objects and to compute sky value of the group.

    """
    def __init__(self, images, id=None, sky=0.0):

        if isinstance(images, SkyImage):
            self._images = [images]

        elif hasattr(images, '__iter__'):
            self._images = []
            for im in images:
                if not isinstance(im, SkyImage):
                    raise TypeError("Each element of the 'images' parameter "
                                    "must be an 'SkyImage' object.")
                self._images.append(im)

        else:
            raise TypeError("Parameter 'images' must be either a single "
                            "'SkyImage' object or a list of 'SkyImage' objects")

        self._id = id
        self._update_bounding_polygon()
        self._sky = sky
        for im in self._images:
            im.sky += sky

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

            While `id` can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        delta_sky = sky - self._sky
        self._sky = sky
        for im in self._images:
            im.sky += delta_sky

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.

        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def _update_bounding_polygon(self):
        polygons = [im.polygon for im in self._images]
        if len(polygons) == 0:
            self._polygon = SphericalPolygon([])
            self._radec = []
        else:
            self._polygon = SphericalPolygon.multi_union(polygons)
            self._radec = list(self._polygon.to_radec())

    def __len__(self):
        return len(self._images)

    def __getitem__(self, idx):
        return self._images[idx]

    def __setitem__(self, idx, value):
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images[idx] = value
        self._update_bounding_polygon()

    def __delitem__(self, idx):
        del self._images[idx]
        if len(self._images) == 0:
            self._sky = 0.0
            self._id = None
        self._update_bounding_polygon()

    def __iter__(self):
        for image in self._images:
            yield image

    def insert(self, idx, value):
        """Inserts a `SkyImage` into the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.insert(idx, value)
        self._update_bounding_polygon()

    def append(self, value):
        """Appends a `SkyImage` to the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.append(value)
        self._update_bounding_polygon()

    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if len(self._images) == 0:
            return (None, 0, 0.0)

        wght = 0
        area = 0.0

        if overlap is None:
            # compute minimum sky across all images in the group:
            wsky = None

            for image in self._images:
                # make sure all images have the same background:
                image.background = self._sky

                sky, npix, imarea = image.calc_sky(overlap=None, delta=delta)

                if sky is None:
                    continue

                if wsky is None or wsky > sky:
                    wsky = sky
                    wght = npix
                    area = imarea

            return (wsky, wght, area)

        ################################################
        ##  compute weighted sky in various overlaps: ##
        ################################################
        wsky = 0.0

        for image in self._images:
            # make sure all images have the same background:
            image.background = self._sky

            sky, npix, area1 = image.calc_sky(overlap=overlap, delta=delta)

            area += area1

            if sky is not None and npix > 0:
                pix_area = npix * image.pix_area
                wsky += sky * pix_area
                wght += pix_area

        if wght == 0.0 or area == 0.0:
            return (None, wght, area)
        else:
            return (wsky / wght, wght, area)