Example #1
0
    def label_islands(self, detectionthresholdmap, analysisthresholdmap):
        """
        Return a lablled array of pixels for fitting.

        Args:

            detectionthresholdmap (numpy.ndarray):

            analysisthresholdmap (numpy.ndarray):

        Returns:

            list of valid islands (list of int)

            labelled islands (numpy.ndarray)
        """
        # If there is no usable data, we return an empty set of islands.
        if not len(self.rmsmap.compressed()):
            logging.warning("RMS map masked; sourcefinding skipped")
            return [], numpy.zeros(self.data_bgsubbed.shape, dtype=numpy.int)

        # At this point, we select all the data which is eligible for
        # sourcefitting. We are actually using three separate filters, which
        # exclude:
        #
        # 1. Anything which has been masked before we reach this point;
        # 2. Any pixels which fall below the analysis threshold at that pixel
        #    position;
        # 3. Any pixels corresponding to a position where the RMS noise is
        #    less than RMS_FILTER (default 0.001) times the median RMS across
        #    the whole image.
        #
        # The third filter attempts to exclude those regions of the image
        # which contain no usable data; for example, the parts of the image
        # falling outside the circular region produced by awimager.
        RMS_FILTER = 0.001
        clipped_data = numpy.ma.where(
            (self.data_bgsubbed > analysisthresholdmap) &
            (self.rmsmap >= (RMS_FILTER * numpy.ma.median(self.rmsmap))), 1,
            0).filled(fill_value=0)
        labelled_data, num_labels = ndimage.label(clipped_data,
                                                  STRUCTURING_ELEMENT)

        labels_below_det_thr, labels_above_det_thr = [], []
        if num_labels > 0:
            # Select the labels of the islands above the analysis threshold
            # that have maximum values values above the detection threshold.
            # Like above we make sure not to select anything where either
            # the data or the noise map are masked.
            # We fill these pixels in above_det_thr with -1 to make sure
            # its labels will not be in labels_above_det_thr.
            # NB data_bgsubbed, and hence above_det_thr, is a masked array;
            # filled() sets all mased values equal to -1.
            above_det_thr = (self.data_bgsubbed -
                             detectionthresholdmap).filled(fill_value=-1)
            # Note that we avoid label 0 (the background).
            maximum_values = ndimage.maximum(above_det_thr, labelled_data,
                                             numpy.arange(1, num_labels + 1))

            # If there's only one island, ndimage.maximum will return a float,
            # rather than a list. The rest of this function assumes that it's
            # always a list, so we need to convert it.
            if isinstance(maximum_values, float):
                maximum_values = [maximum_values]

            # We'll filter out the insignificant islands
            for i, x in enumerate(maximum_values, 1):
                if x < 0:
                    labels_below_det_thr.append(i)
                else:
                    labels_above_det_thr.append(i)
            # Set to zero all labelled islands that are below det_thr:
            labelled_data = numpy.where(
                numpy.in1d(labelled_data.ravel(),
                           labels_above_det_thr).reshape(labelled_data.shape),
                labelled_data, 0)

        return labels_above_det_thr, labelled_data
Example #2
0
    def deblend(self, niter=0):
        """Return a decomposed numpy array of all the subislands.

        Iterate up through subthresholds, looking for our island
        splitting into two. If it does, start again, with two or more
        separate islands.
        """

        logger.debug("Deblending source")
        for level in self.subthrrange[niter:]:

            # The idea is to retain the parent island when no significant
            # subislands are found and jump to the next subthreshold
            # using niter.
            # Deblending is started at a level higher than the lowest
            # pixel value in the island.
            # Deblending at the level of the lowest pixel value will
            # likely not yield anything, because the island was formed at
            # threshold just below that.
            # So that is why we use niter+1 (>=1) instead of niter (>=0).

            if level > self.data.max():
                # level is above the highest pixel value...
                # Return the current island.
                break
            clipped_data = numpy.where(
                self.data.filled(fill_value=0) >= level, 1, 0)
            labels, number = ndimage.label((clipped_data),
                                           self.structuring_element)
            # If we have more than one island, then we need to make subislands.
            if number > 1:
                subislands = []
                label = 0
                for chunk in ndimage.find_objects(labels):
                    label += 1
                    newdata = numpy.where(labels == label,
                                          self.data.filled(fill_value=-BIGNUM),
                                          -BIGNUM)
                    # NB: In class Island(object), rms * analysis_threshold
                    # is taken as the threshold for the bottom of the island.
                    # Everything below that level is masked.
                    # For subislands, this product should be equal to level
                    # and flat, i.e., horizontal.
                    # We can achieve this by setting rms=level*ones and
                    # analysis_threshold=1.
                    island = Island(
                        newdata[chunk],
                        (numpy.ones(self.data[chunk].shape) * level),
                        (slice(self.chunk[0].start + chunk[0].start,
                               self.chunk[0].start + chunk[0].stop),
                         slice(self.chunk[1].start + chunk[1].start,
                               self.chunk[1].start + chunk[1].stop)), 1,
                        self.detection_map[chunk], self.beam,
                        self.deblend_nthresh, self.deblend_mincont,
                        self.structuring_element,
                        self.rms_orig[chunk[0].start:chunk[0].stop,
                                      chunk[1].start:chunk[1].stop],
                        self.flux_orig, self.subthrrange)

                    subislands.append(island)
                # This line should filter out any subisland with insufficient
                # flux, in about the same way as SExtractor.
                # Sufficient means: the flux of the branch above the
                # subthreshold (=level) must exceed some user given fraction
                # of the composite object, i.e., the original island.
                subislands = [
                    isl for isl in subislands
                    if (isl.data - numpy.ma.array(
                        numpy.ones(isl.data.shape) * level, mask=isl.data.mask)
                        ).sum() > self.deblend_mincont * self.flux_orig
                ]
                # Discard subislands below detection threshold
                subislands = [
                    isl for isl in subislands
                    if (isl.data - isl.detection_map).max() >= 0
                ]
                numbersignifsub = len(subislands)
                # Proceed with the previous island, but make sure the next
                # subthreshold is higher than the present one.
                # Or we would end up in an infinite loop...
                if numbersignifsub > 1:
                    if niter + 1 < self.deblend_nthresh:
                        # Apparently, the map command always results in
                        # nested lists.
                        return list(
                            utils.flatten([
                                island.deblend(niter=niter + 1)
                                for island in subislands
                            ]))
                    else:
                        return subislands
                elif numbersignifsub == 1 and niter + 1 < self.deblend_nthresh:
                    return Island.deblend(self, niter=niter + 1)
                else:
                    # In this case we have numbersignifsub == 0 or
                    # (1 and reached the highest subthreshold level).
                    # Pull out of deblending loop, return current island.
                    break
        # We've not found any subislands: just return this island.
        return self
Example #3
0
    def fit_to_point(self, x, y, boxsize, threshold, fixed):
        """Fit an elliptical Gaussian to a specified point on the image.

        The fit is carried on a square section of the image, of length
        *boxsize* & centred at pixel coordinates *x*, *y*. Any data
        below *threshold* * rmsmap is not used for fitting. If *fixed*
        is set to ``position``, then the pixel coordinates are fixed
        in the fit.

        Returns an instance of :class:`sourcefinder.extract.Detection`.
        """

        logger.debug("Force-fitting pixel location ({},{})".format(x, y))
        # First, check that x and y are actually valid semi-positive integers.
        # Otherwise,
        # If they are too high (positive), then indexing will fail
        # BUT, if they are negative, then we get wrap-around indexing
        # and the fit continues at the wrong position!
        if (x < 0 or x > self.xdim or y < 0 or y > self.ydim):
            logger.warning("Dropping forced fit at ({},{}), "
                           "pixel position outside image".format(x, y))
            return None

        # Next, check if any of the central pixels (in a 3x3 box about the
        # fitted pixel position) have been Masked
        # (e.g. if NaNs, or close to image edge) - reject if so.
        central_pixels_slice = ImageData.box_slice_about_pixel(x, y, 1)
        if self.data.mask[central_pixels_slice].any():
            logger.warning("Dropping forced fit at ({},{}), "
                           "Masked pixel in central fitting region".format(
                               x, y))
            return None

        if ((
                # Recent NumPy
                hasattr(numpy.ma.core, "MaskedConstant")
                and isinstance(self.rmsmap, numpy.ma.core.MaskedConstant)) or (
                    # Old NumPy
                    numpy.ma.is_masked(self.rmsmap[int(x), int(y)]))):
            logger.error("Background is masked: cannot fit")
            return None

        chunk = ImageData.box_slice_about_pixel(x, y, boxsize / 2.0)
        if threshold is not None:
            # We'll mask out anything below threshold*self.rmsmap from the fit.
            labels, num = self.labels.setdefault(
                # Dictionary mapping threshold -> islands map
                threshold,
                ndimage.label(
                    self.clip.setdefault(
                        # Dictionary mapping threshold -> mask
                        threshold,
                        numpy.where(
                            self.data_bgsubbed > threshold * self.rmsmap, 1,
                            0))))

            mylabel = labels[int(x), int(y)]
            if mylabel == 0:  # 'Background'
                raise ValueError(
                    "Fit region is below specified threshold, fit aborted.")
            mask = numpy.where(labels[chunk] == mylabel, 0, 1)
            fitme = numpy.ma.array(self.data_bgsubbed[chunk], mask=mask)
            if len(fitme.compressed()) < 1:
                raise IndexError("Fit region too close to edge or too small")
        else:
            fitme = self.data_bgsubbed[chunk]
            if fitme.size < 1:
                raise IndexError("Fit region too close to edge or too small")

        if not len(fitme.compressed()):
            logger.error("All data is masked: cannot fit")
            return None

        # set argument for fixed parameters based on input string
        if fixed == 'position':
            fixed = {'xbar': boxsize / 2.0, 'ybar': boxsize / 2.0}
        elif fixed == 'position+shape':
            fixed = {
                'xbar': boxsize / 2.0,
                'ybar': boxsize / 2.0,
                'semimajor': self.beam[0],
                'semiminor': self.beam[1],
                'theta': self.beam[2]
            }
        elif fixed == None:
            fixed = {}
        else:
            raise TypeError("Unkown fixed parameter")

        if threshold is not None:
            threshold_at_pixel = threshold * self.rmsmap[int(x), int(y)]
        else:
            threshold_at_pixel = None

        try:
            measurement, residuals = extract.source_profile_and_errors(
                fitme,
                threshold_at_pixel,
                self.rmsmap[int(x), int(y)],
                self.beam,
                fixed=fixed)
        except ValueError:
            # Fit failed to converge
            # Moments are not applicable when holding parameters fixed
            logger.error("Gaussian fit failed at %f, %f", x, y)
            return None

        try:
            assert (abs(measurement['xbar']) < boxsize)
            assert (abs(measurement['ybar']) < boxsize)
        except AssertionError:
            logger.warn('Fit falls outside of box.')

        measurement['xbar'] += x - boxsize / 2.0
        measurement['ybar'] += y - boxsize / 2.0
        measurement.sig = (fitme / self.rmsmap[chunk]).max()

        return extract.Detection(measurement, self)
Example #4
0
File: image.py Project: mkuiack/tkp
    def label_islands(self, detectionthresholdmap, analysisthresholdmap):
        """
        Return a lablled array of pixels for fitting.

        Args:

            detectionthresholdmap (numpy.ndarray):

            analysisthresholdmap (numpy.ndarray):

        Returns:

            list of valid islands (list of int)

            labelled islands (numpy.ndarray)
        """
        # If there is no usable data, we return an empty set of islands.
        if not len(self.rmsmap.compressed()):
            logging.warning("RMS map masked; sourcefinding skipped")
            return [], numpy.zeros(self.data_bgsubbed.shape, dtype=numpy.int)

        # At this point, we select all the data which is eligible for
        # sourcefitting. We are actually using three separate filters, which
        # exclude:
        #
        # 1. Anything which has been masked before we reach this point;
        # 2. Any pixels which fall below the analysis threshold at that pixel
        #    position;
        # 3. Any pixels corresponding to a position where the RMS noise is
        #    less than RMS_FILTER (default 0.001) times the median RMS across
        #    the whole image.
        #
        # The third filter attempts to exclude those regions of the image
        # which contain no usable data; for example, the parts of the image
        # falling outside the circular region produced by awimager.
        RMS_FILTER = 0.001
        clipped_data = numpy.ma.where(
            (self.data_bgsubbed > analysisthresholdmap) &
            (self.rmsmap >= (RMS_FILTER * numpy.ma.median(self.rmsmap))),
            1, 0
        ).filled(fill_value=0)
        labelled_data, num_labels = ndimage.label(clipped_data, STRUCTURING_ELEMENT)

        labels_below_det_thr, labels_above_det_thr = [], []
        if num_labels > 0:
            # Select the labels of the islands above the analysis threshold
            # that have maximum values values above the detection threshold.
            # Like above we make sure not to select anything where either
            # the data or the noise map are masked.
            # We fill these pixels in above_det_thr with -1 to make sure
            # its labels will not be in labels_above_det_thr.
            # NB data_bgsubbed, and hence above_det_thr, is a masked array;
            # filled() sets all mased values equal to -1.
            above_det_thr = (
                self.data_bgsubbed - detectionthresholdmap
            ).filled(fill_value=-1)
            # Note that we avoid label 0 (the background).
            maximum_values = ndimage.maximum(
                above_det_thr, labelled_data, numpy.arange(1, num_labels + 1)
            )

            # If there's only one island, ndimage.maximum will return a float,
            # rather than a list. The rest of this function assumes that it's
            # always a list, so we need to convert it.
            if isinstance(maximum_values, float):
                maximum_values = [maximum_values]

            # We'll filter out the insignificant islands
            for i, x in enumerate(maximum_values, 1):
                if x < 0:
                    labels_below_det_thr.append(i)
                else:
                    labels_above_det_thr.append(i)
            # Set to zero all labelled islands that are below det_thr:
            labelled_data = numpy.where(
                numpy.in1d(labelled_data.ravel(), labels_above_det_thr).reshape(labelled_data.shape),
                labelled_data, 0
            )

        return labels_above_det_thr, labelled_data
Example #5
0
File: image.py Project: mkuiack/tkp
    def fit_to_point(self, x, y, boxsize, threshold, fixed):
        """Fit an elliptical Gaussian to a specified point on the image.

        The fit is carried on a square section of the image, of length
        *boxsize* & centred at pixel coordinates *x*, *y*. Any data
        below *threshold* * rmsmap is not used for fitting. If *fixed*
        is set to ``position``, then the pixel coordinates are fixed
        in the fit.

        Returns an instance of :class:`tkp.sourcefinder.extract.Detection`.
        """
        if ((
                # Recent NumPy
                hasattr(numpy.ma.core, "MaskedConstant") and
                isinstance(self.rmsmap, numpy.ma.core.MaskedConstant)
            ) or (
                # Old NumPy
                numpy.ma.is_masked(self.rmsmap[x, y])
        )):
            logger.error("Background is masked: cannot fit")
            return None

        chunk = ImageData.box_slice_about_pixel(x, y, boxsize/2.0)
        if threshold is not None:
            # We'll mask out anything below threshold*self.rmsmap from the fit.
            labels, num = self.labels.setdefault( #Dictionary mapping threshold -> islands map
                threshold,
                ndimage.label(
                    self.clip.setdefault( #Dictionary mapping threshold -> mask
                        threshold,
                        numpy.where(
                            self.data_bgsubbed > threshold * self.rmsmap, 1, 0
                            )
                        )
                    )
                )


            mylabel = labels[x, y]
            if mylabel == 0:  # 'Background'
                raise ValueError("Fit region is below specified threshold, fit aborted.")
            mask = numpy.where(labels[chunk] == mylabel, 0, 1)
            fitme = numpy.ma.array(self.data_bgsubbed[chunk], mask=mask)
            if len(fitme.compressed()) < 1:
                raise IndexError("Fit region too close to edge or too small")
        else:
            fitme = self.data_bgsubbed[chunk]
            if fitme.size < 1:
                raise IndexError("Fit region too close to edge or too small")

        if not len(fitme.compressed()):
            logger.error("All data is masked: cannot fit")
            return None

        # set argument for fixed parameters based on input string
        if fixed == 'position':
            fixed = {'xbar': boxsize/2.0, 'ybar': boxsize/2.0}
        elif fixed == 'position+shape':
            fixed = {'xbar': boxsize/2.0, 'ybar': boxsize/2.0,
                     'semimajor': self.beam[0],
                     'semiminor': self.beam[1],
                     'theta': self.beam[2]}
        elif fixed == None:
            fixed = {}
        else:
            raise TypeError("Unkown fixed parameter")

        if threshold is not None:
            threshold_at_pixel = threshold * self.rmsmap[x, y]
        else:
            threshold_at_pixel = None

        try:
            measurement, residuals = extract.source_profile_and_errors(
                fitme,
                threshold_at_pixel,
                self.rmsmap[x, y],
                self.beam,
                fixed=fixed
            )
        except ValueError:
            # Fit failed to converge
            # Moments are not applicable when holding parameters fixed
            logger.error("Gaussian fit failed at %f, %f", x, y)
            return None

        try:
            assert(abs(measurement['xbar']) < boxsize)
            assert(abs(measurement['ybar']) < boxsize)
        except AssertionError:
            logger.warn('Fit falls outside of box.')

        measurement['xbar'] += x-boxsize/2.0
        measurement['ybar'] += y-boxsize/2.0
        measurement.sig = (fitme / self.rmsmap[chunk]).max()

        return extract.Detection(measurement, self)
Example #6
0
    def fit_to_point(self, x, y, boxsize, threshold, fixed):
        """Fit an elliptical Gaussian to a specified point on the image.

        The fit is carried on a square section of the image, of length
        *boxsize* & centred at pixel coordinates *x*, *y*. Any data
        below *threshold* * rmsmap is not used for fitting. If *fixed*
        is set to ``position``, then the pixel coordinates are fixed
        in the fit.

        Returns an instance of :class:`tkp.sourcefinder.extract.Detection`.
        """

        logger.debug("Force-fitting pixel location ({},{})".format(x, y))
        # First, check that x and y are actually valid semi-positive integers.
        # Otherwise,
        # If they are too high (positive), then indexing will fail
        # BUT, if they are negative, then we get wrap-around indexing
        # and the fit continues at the wrong position!
        if (x < 0 or x >self.xdim
            or y < 0 or y >self.ydim ):
            logger.warning("Dropping forced fit at ({},{}), "
                           "pixel position outside image".format(x,y)
            )
            return None

        # Next, check if any of the central pixels (in a 3x3 box about the
        # fitted pixel position) have been Masked
        # (e.g. if NaNs, or close to image edge) - reject if so.
        central_pixels_slice = ImageData.box_slice_about_pixel(x, y, 1)
        if self.data.mask[central_pixels_slice].any():
            logger.warning(
                "Dropping forced fit at ({},{}), "
                   "Masked pixel in central fitting region".format(x,y))
            return None

        if ((
                # Recent NumPy
                hasattr(numpy.ma.core, "MaskedConstant") and
                isinstance(self.rmsmap, numpy.ma.core.MaskedConstant)
            ) or (
                # Old NumPy
                numpy.ma.is_masked(self.rmsmap[int(x), int(y)])
        )):
            logger.error("Background is masked: cannot fit")
            return None

        chunk = ImageData.box_slice_about_pixel(x, y, boxsize/2.0)
        if threshold is not None:
            # We'll mask out anything below threshold*self.rmsmap from the fit.
            labels, num = self.labels.setdefault( #Dictionary mapping threshold -> islands map
                threshold,
                ndimage.label(
                    self.clip.setdefault( #Dictionary mapping threshold -> mask
                        threshold,
                        numpy.where(
                            self.data_bgsubbed > threshold * self.rmsmap, 1, 0
                            )
                        )
                    )
                )

            mylabel = labels[int(x), int(y)]
            if mylabel == 0:  # 'Background'
                raise ValueError("Fit region is below specified threshold, fit aborted.")
            mask = numpy.where(labels[chunk] == mylabel, 0, 1)
            fitme = numpy.ma.array(self.data_bgsubbed[chunk], mask=mask)
            if len(fitme.compressed()) < 1:
                raise IndexError("Fit region too close to edge or too small")
        else:
            fitme = self.data_bgsubbed[chunk]
            if fitme.size < 1:
                raise IndexError("Fit region too close to edge or too small")

        if not len(fitme.compressed()):
            logger.error("All data is masked: cannot fit")
            return None

        # set argument for fixed parameters based on input string
        if fixed == 'position':
            fixed = {'xbar': boxsize/2.0, 'ybar': boxsize/2.0}
        elif fixed == 'position+shape':
            fixed = {'xbar': boxsize/2.0, 'ybar': boxsize/2.0,
                     'semimajor': self.beam[0],
                     'semiminor': self.beam[1],
                     'theta': self.beam[2]}
        elif fixed == None:
            fixed = {}
        else:
            raise TypeError("Unkown fixed parameter")

        if threshold is not None:
            threshold_at_pixel = threshold * self.rmsmap[int(x), int(y)]
        else:
            threshold_at_pixel = None

        try:
            measurement, residuals = extract.source_profile_and_errors(
                fitme,
                threshold_at_pixel,
                self.rmsmap[int(x), int(y)],
                self.beam,
                fixed=fixed
            )
        except ValueError:
            # Fit failed to converge
            # Moments are not applicable when holding parameters fixed
            logger.error("Gaussian fit failed at %f, %f", x, y)
            return None

        try:
            assert(abs(measurement['xbar']) < boxsize)
            assert(abs(measurement['ybar']) < boxsize)
        except AssertionError:
            logger.warn('Fit falls outside of box.')

        measurement['xbar'] += x-boxsize/2.0
        measurement['ybar'] += y-boxsize/2.0
        measurement.sig = (fitme / self.rmsmap[chunk]).max()

        return extract.Detection(measurement, self)
Example #7
0
    def fit_to_point(self, x, y, boxsize, threshold, fixed):
        """Fit an elliptical Gaussian to a specified point on the image.

        The fit is carried on a square section of the image, of length
        *boxsize* & centred at pixel coordinates *x*, *y*. Any data
        below *threshold* * rmsmap is not used for fitting. If *fixed*
        is set to ``position``, then the pixel coordinates are fixed
        in the fit.

        Returns an instance of :class:`tkp.sourcefinder.extract.Detection`.
        """
        if ((
                # Recent NumPy
                hasattr(numpy.ma.core, "MaskedConstant")
                and isinstance(self.rmsmap, numpy.ma.core.MaskedConstant)) or (
                    # Old NumPy
                    numpy.ma.is_masked(self.rmsmap[x, y]))):
            logger.error("Background is masked: cannot fit")
            return None

        chunk = ImageData.box_slice_about_pixel(x, y, boxsize / 2.0)
        if threshold is not None:
            # We'll mask out anything below threshold*self.rmsmap from the fit.
            labels, num = self.labels.setdefault(  #Dictionary mapping threshold -> islands map
                threshold,
                ndimage.label(
                    self.clip.
                    setdefault(  #Dictionary mapping threshold -> mask
                        threshold,
                        numpy.where(
                            self.data_bgsubbed > threshold * self.rmsmap, 1,
                            0))))

            mylabel = labels[x, y]
            if mylabel == 0:  # 'Background'
                raise ValueError(
                    "Fit region is below specified threshold, fit aborted.")
            mask = numpy.where(labels[chunk] == mylabel, 0, 1)
            fitme = numpy.ma.array(self.data_bgsubbed[chunk], mask=mask)
            if len(fitme.compressed()) < 1:
                raise IndexError("Fit region too close to edge or too small")
        else:
            fitme = self.data_bgsubbed[chunk]
            if fitme.size < 1:
                raise IndexError("Fit region too close to edge or too small")

        if not len(fitme.compressed()):
            logger.error("All data is masked: cannot fit")
            return None

        # set argument for fixed parameters based on input string
        if fixed == 'position':
            fixed = {'xbar': boxsize / 2.0, 'ybar': boxsize / 2.0}
        elif fixed == 'position+shape':
            fixed = {
                'xbar': boxsize / 2.0,
                'ybar': boxsize / 2.0,
                'semimajor': self.beam[0],
                'semiminor': self.beam[1],
                'theta': self.beam[2]
            }
        elif fixed == None:
            fixed = {}
        else:
            raise TypeError("Unkown fixed parameter")

        if threshold is not None:
            threshold_at_pixel = threshold * self.rmsmap[x, y]
        else:
            threshold_at_pixel = None

        try:
            measurement, residuals = extract.source_profile_and_errors(
                fitme,
                threshold_at_pixel,
                self.rmsmap[x, y],
                self.beam,
                fixed=fixed)
        except ValueError:
            # Fit failed to converge
            # Moments are not applicable when holding parameters fixed
            logger.error("Gaussian fit failed at %f, %f", x, y)
            return None

        try:
            assert (abs(measurement['xbar']) < boxsize)
            assert (abs(measurement['ybar']) < boxsize)
        except AssertionError:
            logger.warn('Fit falls outside of box.')

        measurement['xbar'] += x - boxsize / 2.0
        measurement['ybar'] += y - boxsize / 2.0
        measurement.sig = (fitme / self.rmsmap[chunk]).max()

        return extract.Detection(measurement, self)