Exemplo n.º 1
0
def find_sources_via_segmentation(data,
                                  sigma=3,
                                  fwhm=2.0,
                                  min_pix=5,
                                  make_plot=False):
    """
    """
    yd, xd = data.shape

    # Let's define the background using boxes of ~50x50 pixels
    nboxx = int(xd / 150)
    nboxy = int(yd / 150)
    bkg_estimator = MedianBackground()
    bkg = Background2D(data, (nboxy, nboxx),
                       filter_size=(3, 3),
                       bkg_estimator=bkg_estimator)

    data -= bkg.background  # subtract the background
    threshold = sigma * bkg.background_rms

    #threshold = detect_threshold(data, nsigma=sigma)

    gaussian_sigma = fwhm * gaussian_fwhm_to_sigma
    kernel = Gaussian2DKernel(gaussian_sigma, x_size=3, y_size=3)
    kernel.normalize(mode='integral')
    segm = detect_sources(data,
                          threshold,
                          npixels=min_pix,
                          filter_kernel=kernel)
    segm_deblend = deblend_sources(data,
                                   segm,
                                   npixels=min_pix,
                                   filter_kernel=kernel,
                                   nlevels=32,
                                   contrast=0.001)

    if make_plot:
        norm = ImageNormalize(stretch=SqrtStretch())
        fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 12.5))
        ax1.imshow(data, origin='lower', cmap='Greys_r', norm=norm)
        ax1.set_title('Data')
        cmap = segm.make_cmap(seed=123)
        ax2.imshow(segm, origin='lower', cmap=cmap, interpolation='nearest')
        ax2.set_title('Segmentation Image')
        ax3.imshow(segm_deblend,
                   origin='lower',
                   cmap=cmap,
                   interpolation='nearest')
        ax3.set_title('Deblended Segmentation Image')
        plt.show()
        #plt.save('testing.jpg')

    return data, segm_deblend, bkg
Exemplo n.º 2
0
def search_input_position(cutout):

    sigma = 3.0 * gaussian_fwhm_to_sigma
    kernel = Gaussian2DKernel(sigma, x_size=3, y_size=3)
    kernel.normalize()
    threshold = detect_threshold(cutout.data, nsigma=2.)
    segm = detect_sources(cutout.data,
                          threshold,
                          npixels=5,
                          filter_kernel=kernel)

    segm_deblend = deblend_sources(cutout.data,
                                   segm,
                                   npixels=5,
                                   filter_kernel=kernel,
                                   nlevels=5,
                                   contrast=0.001)

    idx = np.random.choice(
        np.arange(len((np.where(segm_deblend.data == 0)[0]))), 1)
    x = np.where(segm_deblend.data == 0)[0][idx][0]
    y = np.where(segm_deblend.data == 0)[0][idx][0]

    return cutout.wcs.pixel_to_world(x, y)
Exemplo n.º 3
0
def make_segment_img(data,
                     threshold,
                     npixels=5.0,
                     kernel=None,
                     mask=None,
                     deblend=False):
    """
    Detect sources in an image, including deblending.

    Parameters
    ----------
    data : 2D `~numpy.ndarray`
        The input 2D array.

    threshold : float
        The data value or pixel-wise data values to be used for the
        detection threshold. A 2D threshold must have the same shape as
        ``data``.

    npixels : int
        The number of connected pixels, each greater than ``threshold``
        that an object must have to be detected.  ``npixels`` must be a
        positive integer.

    kernel : `astropy.convolution.Kernel2D`
        The filtering kernel.  Filtering the image will smooth the noise
        and maximize detectability of objects with a shape similar to
        the kernel.

    mask : array_like of bool, optional
        A boolean mask, with the same shape as the input ``data``, where
        `True` values indicate masked pixels.  Masked pixels will not be
        included in any source.

    deblend : bool, optional
        Whether to deblend overlapping sources.  Source deblending
        requires scikit-image.

    Returns
    -------
    segment_image : `~photutils.segmentation.SegmentationImage` or `None`
        A 2D segmentation image, with the same shape as the input data,
        where sources are marked by different positive integer values.
        A value of zero is reserved for the background.  If no sources
        are found then `None` is returned.
    """
    connectivity = 8
    segm = detect_sources(data,
                          threshold,
                          npixels,
                          filter_kernel=kernel,
                          mask=mask,
                          connectivity=connectivity)
    if segm is None:
        return None

    # source deblending requires scikit-image
    if deblend:
        nlevels = 32
        contrast = 0.001
        mode = 'exponential'
        segm = deblend_sources(data,
                               segm,
                               npixels=npixels,
                               filter_kernel=kernel,
                               nlevels=nlevels,
                               contrast=contrast,
                               mode=mode,
                               connectivity=connectivity,
                               relabel=True)

    return segm
Exemplo n.º 4
0
    def find_saturation(self, frame, config, default_fwhm, star_mask=None):
        """
        This function ...
        :param frame:
        :param config:
        :param default_fwhm:
        :param star_mask:
        :return:
        """

        # Convert FWHM to sigma
        default_sigma = default_fwhm * statistics.fwhm_to_sigma

        # Determine the radius for the saturation detection
        model = self.psf_model
        radius = fitting.sigma(
            model
        ) * config.sigmas if model is not None else default_sigma * config.sigmas

        # Make sure the radius is never smaller than 4 pixels
        radius = max(radius, 4.)

        # Add a new stage to the track record
        #if self.has_track_record: self.track_record.set_stage("saturation")

        # Look for a center segment corresponding to a 'saturation' source
        radius_ellipse = PixelStretch(radius, radius)
        ellipse = PixelEllipseRegion(self.pixel_position(frame.wcs),
                                     radius_ellipse)

        # frame_star_erased = frame.copy()
        # frame_star_erased[self.source.y_slice, self.source.x_slice][self.source.mask] = 0.0

        # saturation_source = sources.find_source_segmentation(frame, ellipse, config, track_record=self.track_record, special=self.special)
        # saturation_source = sources.find_source_segmentation(frame_star_erased, ellipse, config, track_record=self.track_record, special=self.special)

        mask_cutout = CutoutMask(self.detection.mask, self.detection.x_min,
                                 self.detection.x_max, self.detection.y_min,
                                 self.detection.y_max)
        track_record = None
        saturation_source = sources.find_source_segmentation(
            frame,
            ellipse,
            config,
            track_record=track_record,
            special=self.special)

        # Check if the found source segment is larger than the PSF source
        if saturation_source is not None:

            mask_saturation = CutoutMask(saturation_source.mask,
                                         saturation_source.x_min,
                                         saturation_source.x_max,
                                         saturation_source.y_min,
                                         saturation_source.y_max)
            mask_saturation_as_cutout = mask_saturation.as_cutout(mask_cutout)
            if self.detection.mask.covers(mask_saturation_as_cutout):
                saturation_source = None

        # If a 'saturation' source was found
        if saturation_source is not None:

            if self.special: log.debug("Initial saturation source found")

            x_min = saturation_source.x_min
            x_max = saturation_source.x_max
            y_min = saturation_source.y_min
            y_max = saturation_source.y_max

            # DEBLEND FIRST
            if config.deblend:

                import numpy as np
                from photutils.segmentation import deblend_sources

                # from astropy.convolution import Kernel2D
                # Kernel2D._model = self.psf_model
                # if self.psf_model is not None:
                #     kernelsize = 2 * int(round(fitting.sigma(self.psf_model) * 3.))
                #     print("kernelsize", kernelsize)
                #     kernel = Kernel2D(x_size=kernelsize)
                # else: kernel = None
                kernel = None

                segments = deblend_sources(
                    saturation_source.cutout,
                    saturation_source.mask.astype(int),
                    npixels=config.deblending.min_npixels,
                    contrast=config.deblending.contrast,
                    mode=config.deblending.mode,
                    nlevels=config.deblending.nlevels,
                    filter_kernel=kernel)

                plotting.plot_box(segments)

                smallest_distance = None
                smallest_distance_mask = None
                for index in np.unique(segments)[1:]:

                    where = segments == index
                    fake_box = Cutout(where.astype(int), x_min, x_max, y_min,
                                      y_max)
                    contour = sources.find_contour(fake_box,
                                                   where,
                                                   sigma_level=1)

                    difference = contour.center - self.pixel_position(
                        frame.wcs)
                    distance = difference.norm

                    if smallest_distance is None or distance < smallest_distance:
                        smallest_distance = distance
                        smallest_distance_mask = where

                        # print(index, difference.norm)

                # SET NEW MASK
                saturation_source.mask = smallest_distance_mask

            # AFTER DEBLENDING, CALCULATE CONTOUR
            # Calculate the elliptical contour
            # contour = sources.find_contour(saturation_source.cutout, saturation_source.mask, config.apertures.sigma_level)
            contour = sources.find_contour(
                Cutout(saturation_source.mask.astype(int), x_min, x_max, y_min,
                       y_max), saturation_source.mask,
                config.apertures.sigma_level
            )  # determine the segment properties of the actual mask segment

            # Check whether the source centroid matches the star position
            if config.check_centroid:

                if self.special: log.debug("Checking contour parameters ...")

                # Calculate the offset
                difference = contour.center - self.pixel_position(frame.wcs)

                star_mask_cutout = star_mask[saturation_source.cutout.y_slice,
                                             saturation_source.cutout.x_slice]

                # Remove the mask of this star from the star_mask_cutout
                x_min_cutout = saturation_source.cutout.x_min
                x_max_cutout = saturation_source.cutout.x_max
                y_min_cutout = saturation_source.cutout.y_min
                y_max_cutout = saturation_source.cutout.y_max

                x_min_source = self.detection.cutout.x_min
                x_max_source = self.detection.cutout.x_max
                y_min_source = self.detection.cutout.y_min
                y_max_source = self.detection.cutout.y_max

                try:
                    # plotting.plot_box(star_mask_cutout, title="before removing central source")
                    star_mask_cutout[y_min_source - y_min_cutout:y_max_source -
                                     y_min_cutout,
                                     x_min_source - x_min_cutout:x_max_source -
                                     x_min_cutout][self.detection.mask] = False
                    # plotting.plot_box(star_mask_cutout, title="after removing central source")
                except IndexError:
                    pass
                # plotting.plot_box(frame[saturation_source.y_slice, saturation_source.x_slice])
                # plotting.plot_box(saturation_source.mask)
                # plotting.plot_box(star_mask_cutout)
                # print(star_mask_cutout.shape)
                # plotting.plot_box(saturation_source.cutout)
                # print(saturation_source.cutout.shape)
                # plotting.plot_box(self.source.mask)
                # print(self.source.mask.shape)
                # print(y_min_source, y_min_cutout, y_max_source, y_min_cutout, x_min_source, x_min_cutout, x_max_source, x_min_cutout)
                # print(y_min_source-y_min_cutout) # becomes negative!
                # print(y_max_source-y_min_cutout)
                # print(x_min_source-x_min_cutout) # becomes negative !
                # print(x_max_source-x_min_cutout)
                # print(star_mask_cutout[y_min_source-y_min_cutout:y_max_source-y_min_cutout, x_min_source-x_min_cutout:x_max_source-x_min_cutout].shape)

                # source_mask_smaller = self.source.mask[y_min_cutout-y_min_source:,x_min_cutout-x_min_source]

                # star_mask_cutout[0:y_max_source-y_min_cutout][0:x_max_source-x_min_cutout][source_mask_smaller] = False

                # TODO: fix this problem ! (how can it be that the source box is not inside the saturation box??)
                # saturation sources are created by expanding the initial source box ??

                # Discard this saturation source if the centroid offset or the ellipticity is too large
                if not masks.overlap(saturation_source.mask, star_mask_cutout):
                    if self.special:
                        log.debug("Checking offset and ellipticity")
                    if difference.norm > config.max_centroid_offset or contour.ellipticity > config.max_centroid_ellipticity:
                        if self.special:
                            log.debug(
                                "Found to large offset or ellipticity: not a saturation source"
                            )
                        return
                else:
                    if self.special:
                        log.debug(
                            "Saturation mask overlaps other stars, so contour parameters will not be checked"
                        )

            if config.second_segmentation:

                # Find all of the saturation light in a second segmentation step
                track_record = None
                saturation_source = sources.find_source_segmentation(
                    frame,
                    ellipse,
                    config,
                    track_record=track_record,
                    special=self.special,
                    sigma_level=config.second_sigma_level)
                # contour = sources.find_contour(saturation_source.cutout, saturation_source.mask, config.apertures.sigma_level)
                contour = sources.find_contour(
                    saturation_source.mask.astype(int), saturation_source.mask,
                    config.apertures.sigma_level
                )  # determine the segment properties of the actual mask segment

                # Check whether the source centroid matches the star position
                if config.check_centroid:

                    # Calculate the offset
                    difference = contour.center - self.pixel_position(
                        frame.wcs)

                    star_mask_cutout = star_mask[
                        saturation_source.cutout.y_slice,
                        saturation_source.cutout.x_slice]

                    # Remove the mask of this star from the star_mask_cutout
                    x_min_cutout = saturation_source.cutout.x_min
                    x_max_cutout = saturation_source.cutout.x_max
                    y_min_cutout = saturation_source.cutout.y_min
                    y_max_cutout = saturation_source.cutout.y_max

                    x_min_source = self.detection.cutout.x_min
                    x_max_source = self.detection.cutout.x_max
                    y_min_source = self.detection.cutout.y_min
                    y_max_source = self.detection.cutout.y_max

                    # plotting.plot_box(star_mask_cutout, title="before removing central source")
                    star_mask_cutout[y_min_source - y_min_cutout:y_max_source -
                                     y_min_cutout,
                                     x_min_source - x_min_cutout:x_max_source -
                                     x_min_cutout][self.detection.mask] = False
                    # plotting.plot_box(star_mask_cutout, title="after removing central source")

                    # Discard this saturation source if the centroid offset or the ellipticity is too large
                    if not masks.overlap(saturation_source.mask,
                                         star_mask_cutout):
                        if difference.norm > config.max_centroid_offset or contour.ellipticity > config.max_centroid_ellipticity:
                            return

            # Replace the pixels of the cutout box by the pixels of the original frame (because the star itself is already removed)
            # saturation_source.cutout = frame.box_like(saturation_source.cutout)

            # TODO: check with classifier to verify this is actually a saturation source!

            if self.special:
                saturation_source.plot(title="Final saturation source")

            # Replace the source by a source that covers the saturation
            self.saturation = saturation_source
            self.contour = contour
Exemplo n.º 5
0
def deblend_stars(image,
                  ew_map=None,
                  sigma_thresh=3.,
                  fwhm_pix=3.,
                  npixels=15,
                  nlevels=32,
                  contrast=0.01,
                  size_thresh=2.,
                  dist_to_center_thresh=10.,
                  area_thresh=200.,
                  ew_thresh=20.,
                  output_dir='',
                  save_fits=False):
    """
    This method estimates foreground stars over a given image.
    Additional constrains can be applied by providing an EW(Ha) map, preventing
    giant HII regions to be classified as stars.
    ---------------------------------------------------------------------------
    input params:
        - image: (2D array) Flux image used to deblend the sources
        - ew_map (optional): (2D array) EW(Ha) map used to further
        constrain the sources
        - sigma_thresh (float, default=3.0): number of stddev for estimating
        the signal threshold
        - fwhm_pix (float, default=3.0): FWHM for Gaussian smoothing prior to
        source detection
        - npixels (int, default=15): The number of connected pixels, each
        greater than threshold, that an object must have to be detected.
        - nlevels (int, default=15): The number of multi-thresholding levels
        to use
        - contrast (float, default=0.01)
        - size_thres (float, defualt=2.)
        -
        -
        - output_dir (optional, defatult=''): (str) directory where the results
        will be saved
        - save_filts (optional, default=False): Set True for saving the stellar
        masks
    """
    if ew_map is None:
        ew = np.zeros_like(image)
    rows, columns = image.shape
    centerx, centery = rows // 2, columns // 2
    XX, YY = np.meshgrid(np.arange(columns), np.arange(rows))
    # Estimating the image signal threshold
    threshold = detect_threshold(image, nsigma=sigma_thresh)
    # Kernel smoothing
    # sigma_pixels = FWHM_pixels / conversion_factor
    kernel_sigma = fwhm_pix / (2.0 * np.sqrt(2.0 * np.log(2.0)))
    kernel = Gaussian2DKernel(kernel_sigma,
                              x_size=int(fwhm_pix),
                              y_size=int(fwhm_pix))
    kernel.normalize()
    # Source detection
    segm = detect_sources(image, threshold, npixels, filter_kernel=kernel)
    # Source deblending
    segm_deblend = deblend_sources(image,
                                   segm,
                                   npixels=npixels,
                                   filter_kernel=kernel,
                                   nlevels=nlevels,
                                   contrast=0.01)
    deblended_areas = segm_deblend.areas
    stellar_masks = []

    if deblended_areas.size > 1:
        mask = segm_deblend.data
        cmap = segm_deblend.make_cmap()

        plt.figure()
        plt.imshow(segm_deblend, origin='lower', cmap=cmap)
        plt.colorbar()
        plt.contour(image, levels=20, colors='k', origin='lower')
        for ii in range(deblended_areas.size):
            plt.annotate(r'Pixel area: {}'.format(deblended_areas[ii]),
                         xy=(.02, .95 - ii * 0.05),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         color=cmap(ii + 1),
                         fontsize=9)
        plt.savefig(output_dir + 'deblending_map.png')
        plt.close()

        for ii in range(deblended_areas.size):
            source = mask == ii + 1
            source_area = deblended_areas[ii]
            source_image = np.copy(image)
            source_image[~source] = 0
            pos_x, pos_y = centroid_2dg(source_image)
            dist_to_center = np.sqrt((pos_x - centerx)**2 +
                                     (pos_y - centery)**2)
            amplitude_guess = source_image.max()
            initial_guess = [amplitude_guess, pos_x, pos_y, 3, 3, 0]
            try:
                popt, pcov = curve_fit(
                    gaussian2d,
                    [XX, YY],
                    source_image.ravel(),
                    p0=initial_guess,
                    # bounds=(0, [amplitude_guess*2, 80, 80, 5, 5])
                )
            except:
                print('Error fitting gaussian to data')
                continue
            popt = np.abs(popt)
            sigmax, sigmay = popt[3], popt[4]
            gaussian = gaussian2d([XX, YY], *popt).reshape(image.shape)
            level = gaussian2d([
                popt[1] + 3 * max([popt[3], 2]),
                popt[2] + 3 * max([popt[4], 2])
            ], *popt)
            central_pixels = gaussian2d([popt[1] + sigmax, popt[2] + sigmay],
                                        *popt)
            star_mask = gaussian > level
            central_star_mask = gaussian > central_pixels
            ew_source = ew[central_star_mask]
            median_ew = np.nanmedian(ew_source)
            if np.isnan(median_ew):
                median_ew = 0.
            ellipticity = sigmax / sigmay
            # chi2 = np.sum(chi2[star_mask])
            if (sigmax < size_thresh) & (sigmay < size_thresh):
                # &(ellipticity<1.5)&(ellipticity>0.5)
                if (dist_to_center < dist_to_center_thresh) & (
                        source_area < area_thresh) & (median_ew <= ew_thresh):
                    is_star = True
                    stellar_masks.append(star_mask)
                elif (dist_to_center > dist_to_center_thresh) & (median_ew <=
                                                                 ew_thresh):
                    is_star = True
                    stellar_masks.append(star_mask)
                else:
                    is_star = False
            else:
                is_star = False

            plt.figure(figsize=(4, 4))
            plt.subplot(221)
            plt.plot(XX[0, :], np.sum(source_image, axis=0), 'k')
            plt.plot(XX[0, :], np.sum(gaussian, axis=0), 'r')
            plt.annotate(r'$\sigma_x$={:5.3}'.format(sigmax),
                         xy=(.1, .9),
                         xycoords='axes fraction',
                         ha='left',
                         va='top')
            plt.subplot(224)
            plt.plot(np.sum(source_image, axis=1), YY[:, 0], 'k')
            plt.plot(np.sum(gaussian, axis=1), YY[:, 0], 'r')
            plt.annotate(r'$\sigma_y$={:5.3}'.format(sigmay),
                         xy=(.9, .9),
                         xycoords='axes fraction',
                         ha='right',
                         va='top')
            plt.subplot(223)
            plt.imshow(image, cmap='gist_earth', aspect='auto', origin='lower')
            if is_star:
                plt.plot(pos_x, pos_y, '*', c='lime', markersize=10)
            else:
                plt.plot(pos_x, pos_y, '+', c='lime', markersize=10)
            plt.contour(gaussian, colors='r', levels=level, linewidths=2)
            plt.annotate(r'$\sigma_x/\sigma_y$={:5.3}'.format(ellipticity),
                         xy=(.05, .99),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         fontsize=8)
            plt.annotate(r'$EW(H\alpha)_{50}$' + '={:5.3}'.format(median_ew),
                         xy=(.05, .90),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         fontsize=8)
            plt.savefig(output_dir + 'detection_' + str(ii) + '.png')
            plt.close()
            # plt.xlim(pos_x-15,pos_x+15)
            # plt.ylim(pos_y-15,pos_y+15)

    stellar_masks = np.array(stellar_masks)

    if stellar_masks.size > 0:
        print('-->', stellar_masks.shape[0], ' stars detected')
        total_mask = np.zeros_like(image, dtype=bool)
        for i in range(stellar_masks.shape[0]):
            total_mask = total_mask | np.array(stellar_masks[i, :, :],
                                               dtype=bool)
        if save_fits:
            fits_path = output_dir + 'stellar_mask.fits'
            hdr = fits.Header()
            hdr['COMMENT'] = "Stellar masks"
            image_list = []
            image_list.append(fits.PrimaryHDU(header=hdr))
            for ii in range(stellar_masks.shape[0]):
                image_list.append(
                    fits.ImageHDU(np.array(stellar_masks[ii, :, :],
                                           dtype=int)))
            image_list.append(np.array(total_mask, dtype=int))
            hdu = fits.HDUList(image_list)
            hdu.writeto(fits_path, overwrite=True)
            hdu.close()
            print('File saved as: ' + fits_path)
        return total_mask, stellar_masks.shape[0]
    else:
        return np.zeros_like(image, dtype=bool), 0