コード例 #1
0
    def testMultiplyImages(self):
        """Test multiplication"""
        # Multiply by a MaskedImage
        self.mimage2 *= self.mimage

        self.assertEqual(self.mimage2[0, 0, afwImage.LOCAL],
                         (self.imgVal2*self.imgVal1, self.EDGE,
                          self.varVal2*pow(self.imgVal1, 2) + self.varVal1*pow(self.imgVal2, 2)))

        # Divide a MaskedImage<int> by an Image<int>; this divides the variance Image<float>
        # by an Image<int> in C++
        mimage_i = afwImage.MaskedImageI(self.mimage2.getDimensions())
        mimage_i.set(900, 0x0, 1000.0)
        image_i = afwImage.ImageI(mimage_i.getDimensions(), 2)

        mimage_i *= image_i

        self.assertEqual(mimage_i[0, 0, afwImage.LOCAL], (1800, 0x0, 4000.0))

        # multiply by a scalar
        self.mimage *= self.imgVal1

        self.assertEqual(self.mimage[0, 0, afwImage.LOCAL],
                         (self.imgVal1*self.imgVal1, self.EDGE, self.varVal1*pow(self.imgVal1, 2)))

        self.assertEqual(self.mimage.mask[1, 1, afwImage.LOCAL], self.EDGE)
        self.assertEqual(self.mimage.mask[2, 2, afwImage.LOCAL], 0x0)
コード例 #2
0
    def setUp(self):
        self.val = 10
        self.nRow, self.nCol = 100, 200
        self.sctrl = afwMath.StatisticsControl()

        # Integers
        self.mimgI = afwImage.MaskedImageI(
            afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgI.set(self.val, 0x0, self.val)
        self.imgI = afwImage.ImageI(afwGeom.Extent2I(self.nRow, self.nCol),
                                    self.val)
        # TODO: pybind11, this should probably be ndarray
        self.vecI = [self.val for i in range(self.nRow * self.nCol)]

        # floats
        self.mimgF = afwImage.MaskedImageF(
            afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgF.set(self.val, 0x0, self.val)
        self.imgF = afwImage.ImageF(afwGeom.Extent2I(self.nRow, self.nCol),
                                    self.val)
        # TODO: pybind11, this should probably be ndarray
        self.vecF = [float(self.val) for i in range(self.nRow * self.nCol)]

        # doubles
        self.mimgD = afwImage.MaskedImageD(
            afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgD.set(self.val, 0x0, self.val)
        self.imgD = afwImage.ImageD(afwGeom.Extent2I(self.nRow, self.nCol),
                                    self.val)
        # TODO: pybind11, this should probably be ndarray
        self.vecD = [float(self.val) for i in range(self.nRow * self.nCol)]

        self.imgList = [self.imgI, self.imgF, self.imgD]
        self.mimgList = [self.mimgI, self.mimgF, self.mimgD]
        self.vecList = [self.vecI, self.vecF, self.vecD]
コード例 #3
0
    def testAddImages(self):
        "Test addition"
        # add an image
        self.mimage2 += self.mimage

        self.assertEqual(self.mimage2[0, 0, afwImage.LOCAL], (self.imgVal1 + self.imgVal2, self.EDGE,
                                                              self.varVal1 + self.varVal2))

        # Add an Image<int> to a MaskedImage<int>
        mimage_i = afwImage.MaskedImageI(self.mimage2.getDimensions())
        mimage_i.set(900, 0x0, 1000.0)
        image_i = afwImage.ImageI(mimage_i.getDimensions(), 2)

        mimage_i += image_i

        self.assertEqual(mimage_i[0, 0, afwImage.LOCAL], (902, 0x0, 1000.0))

        # add a scalar
        self.mimage += self.imgVal1

        self.assertEqual(self.mimage[0, 0, afwImage.LOCAL],
                         (2*self.imgVal1, self.EDGE, self.varVal1))

        self.assertEqual(self.mimage.mask[1, 1, afwImage.LOCAL], self.EDGE)
        self.assertEqual(self.mimage.mask[2, 2, afwImage.LOCAL], 0x0)

        # add a function
        self.mimage.set(self.imgVal1, 0x0, 0.0)
        self.mimage += self.function

        for i, j in [(2, 3)]:
            self.assertEqual(self.mimage.image[i, j, afwImage.LOCAL],
                             self.imgVal1 + self.function(i, j))
コード例 #4
0
    def setUp(self):
        self.val = 10
        self.nRow, self.nCol = 100, 200
        self.sctrl = afwMath.StatisticsControl()

        # Integers
        self.mimgI = afwImage.MaskedImageI(afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgI.set(self.val, 0x0, self.val)
        self.imgI = afwImage.ImageI(afwGeom.Extent2I(self.nRow, self.nCol), self.val)
        self.vecI = afwMath.vectorI(self.nRow*self.nCol, self.val)

        # floats
        self.mimgF = afwImage.MaskedImageF(afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgF.set(self.val, 0x0, self.val)
        self.imgF = afwImage.ImageF(afwGeom.Extent2I(self.nRow, self.nCol), self.val)
        self.vecF = afwMath.vectorF(self.nRow*self.nCol, self.val)

        # doubles
        self.mimgD = afwImage.MaskedImageD(afwGeom.Extent2I(self.nRow, self.nCol))
        self.mimgD.set(self.val, 0x0, self.val)
        self.imgD = afwImage.ImageD(afwGeom.Extent2I(self.nRow, self.nCol), self.val)
        self.vecD = afwMath.vectorD(self.nRow*self.nCol, self.val)

        self.imgList  = [self.imgI,  self.imgF,  self.imgD]
        self.mimgList = [self.mimgI, self.mimgF, self.mimgD]
        self.vecList  = [self.vecI,  self.vecF,  self.vecD]
コード例 #5
0
    def integerConvert(image):
        """Return an integer version of the input image.

        Parameters
        ----------
        image : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
            Image to convert to integers.

        Returns
        -------
        outI : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
            The integer converted image.

        Raises
        ------
        RuntimeError
            Raised if the input image could not be converted.
        """
        if hasattr(image, "image"):
            # Is a maskedImage:
            imageI = image.image.convertI()
            outI = afwImage.MaskedImageI(imageI, image.mask, image.variance)
        elif hasattr(image, "convertI"):
            # Is an Image:
            outI = image.convertI()
        elif hasattr(image, "astype"):
            # Is a numpy array:
            outI = image.astype(int)
        else:
            raise RuntimeError("Could not convert this to integers: %s %s %s",
                               image, type(image), dir(image))
        return outI
コード例 #6
0
    def testDivideImages(self):
        """Test division"""
        # Divide by a MaskedImage
        mimage2_copy = self.mimage2.Factory(self.mimage2, True)  # make a copy
        mimage2_copy /= self.mimage

        self.assertEqual(mimage2_copy.getImage().get(0, 0),
                         self.imgVal2 / self.imgVal1)
        self.assertEqual(mimage2_copy.getMask().get(0, 0), self.EDGE)
        self.assertAlmostEqual(mimage2_copy.getVariance().get(0, 0),
                               (self.varVal2 * pow(self.imgVal1, 2) +
                                self.varVal1 * pow(self.imgVal2, 2)) /
                               pow(self.imgVal1, 4), 10)
        # Divide by an Image (of the same type as MaskedImage.getImage())
        mimage = self.mimage2.Factory(self.mimage2, True)
        mimage /= mimage.getImage()

        self.assertEqual(mimage.get(0, 0),
                         (self.imgVal2 / self.imgVal2, 0x0, self.varVal2))

        # Divide by an Image (of a different type from MaskedImage.getImage())
        # this isn't supported from python (it's OK in C++)
        if False:
            mimage = self.mimage2.Factory(self.mimage2, True)
            image = afwImage.ImageI(mimage.getDimensions(), 1)
            mimage /= image

            self.assertEqual(mimage.get(0, 0),
                             (self.imgVal2, 0x0, self.varVal2))

        # Divide a MaskedImage<int> by an Image<int>; this divides the variance Image<float>
        # by an Image<int> in C++
        mimage_i = afwImage.MaskedImageI(self.mimage2.getDimensions())
        mimage_i.set(900, 0x0, 1000.0)
        image_i = afwImage.ImageI(mimage_i.getDimensions(), 2)

        mimage_i /= image_i

        self.assertEqual(mimage_i.get(0, 0), (450, 0x0, 250.0))

        # divide by a scalar
        self.mimage /= self.imgVal1

        self.assertEqual(self.mimage.getImage().get(0, 0),
                         self.imgVal1 / self.imgVal1)
        self.assertEqual(self.mimage.getMask().get(0, 0), self.EDGE)
        self.assertAlmostEqual(self.mimage.getVariance().get(0, 0),
                               self.varVal1 / pow(self.imgVal1, 2), 9)

        self.assertEqual(self.mimage.getMask().get(1, 1), self.EDGE)
        self.assertEqual(self.mimage.getMask().get(2, 2), 0x0)
コード例 #7
0
    def testSubtractImages(self):
        "Test subtraction"
        # subtract an image
        self.mimage2 -= self.mimage
        self.assertEqual(self.mimage2[0, 0, afwImage.LOCAL],
                         (self.imgVal2 - self.imgVal1, self.EDGE, self.varVal2 + self.varVal1))

        # Subtract an Image<int> from a MaskedImage<int>
        mimage_i = afwImage.MaskedImageI(self.mimage2.getDimensions())
        mimage_i.set(900, 0x0, 1000.0)
        image_i = afwImage.ImageI(mimage_i.getDimensions(), 2)

        mimage_i -= image_i

        self.assertEqual(mimage_i[0, 0, afwImage.LOCAL], (898, 0x0, 1000.0))

        # subtract a scalar
        self.mimage -= self.imgVal1
        self.assertEqual(self.mimage[0, 0, afwImage.LOCAL], (0.0, self.EDGE, self.varVal1))
コード例 #8
0
    def measureVectorOverscan(self, image):
        """Calculate the 1-d vector overscan from the input overscan image.

        Parameters
        ----------
        image : `lsst.afw.image.MaskedImage`
            Image containing the overscan data.

        Returns
        -------
        results : `lsst.pipe.base.Struct`
            Overscan result with entries:
            - ``overscanValue``: Overscan value to subtract (`float`)
            - ``maskArray`` : `list` [ `bool` ]
                List of rows that should be masked as ``SUSPECT`` when the
                overscan solution is applied.
            - ``isTransposed`` : `bool`
               Indicates if the overscan data was transposed during
               calcuation, noting along which axis the overscan should be
               subtracted.
        """
        calcImage = self.getImageArray(image)

        # operate on numpy-arrays from here
        calcImage, isTransposed = self.transpose(calcImage)
        masked = self.maskOutliers(calcImage)

        startTime = time.perf_counter()

        if self.config.fitType == 'MEDIAN_PER_ROW':
            mi = afwImage.MaskedImageI(image.getBBox())
            masked = masked.astype(int)
            if isTransposed:
                masked = masked.transpose()

            mi.image.array[:, :] = masked.data[:, :]
            if bool(masked.mask.shape):
                mi.mask.array[:, :] = masked.mask[:, :]

            overscanVector = fitOverscanImage(mi, self.config.maskPlanes,
                                              isTransposed)
            maskArray = self.maskExtrapolated(overscanVector)
        else:
            collapsed = self.collapseArray(masked)

            num = len(collapsed)
            indices = 2.0 * np.arange(num) / float(num) - 1.0

            poly = np.polynomial
            fitter, evaler = {
                'POLY': (poly.polynomial.polyfit, poly.polynomial.polyval),
                'CHEB': (poly.chebyshev.chebfit, poly.chebyshev.chebval),
                'LEG': (poly.legendre.legfit, poly.legendre.legval),
                'NATURAL_SPLINE': (self.splineFit, self.splineEval),
                'CUBIC_SPLINE': (self.splineFit, self.splineEval),
                'AKIMA_SPLINE': (self.splineFit, self.splineEval)
            }[self.config.fitType]

            # These are the polynomial coefficients, or an
            # interpolation object.
            coeffs = fitter(indices, collapsed, self.config.order)

            if isinstance(coeffs, float):
                self.log.warn(
                    "Using fallback value %f due to fitter failure. Amplifier will be masked.",
                    coeffs)
                overscanVector = np.full_like(indices, coeffs)
                maskArray = np.full_like(collapsed, True, dtype=bool)
            else:
                # Otherwise we can just use things as normal.
                overscanVector = evaler(indices, coeffs)
                maskArray = self.maskExtrapolated(collapsed)
        endTime = time.perf_counter()
        self.log.info(
            f"Overscan measurement took {endTime - startTime}s for {self.config.fitType}"
        )
        return pipeBase.Struct(overscanValue=np.array(overscanVector),
                               maskArray=maskArray,
                               isTransposed=isTransposed)
コード例 #9
0
def overscanCorrection(ampMaskedImage,
                       overscanImage,
                       fitType='MEDIAN',
                       order=1,
                       collapseRej=3.0,
                       statControl=None,
                       overscanIsInt=True):
    """Apply overscan correction in place.

    Parameters
    ----------
    ampMaskedImage : `lsst.afw.image.MaskedImage`
        Image of amplifier to correct; modified.
    overscanImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
        Image of overscan; modified.
    fitType : `str`
        Type of fit for overscan correction. May be one of:

        - ``MEAN``: use mean of overscan.
        - ``MEANCLIP``: use clipped mean of overscan.
        - ``MEDIAN``: use median of overscan.
        - ``POLY``: fit with ordinary polynomial.
        - ``CHEB``: fit with Chebyshev polynomial.
        - ``LEG``: fit with Legendre polynomial.
        - ``NATURAL_SPLINE``: fit with natural spline.
        - ``CUBIC_SPLINE``: fit with cubic spline.
        - ``AKIMA_SPLINE``: fit with Akima spline.

    order : `int`
        Polynomial order or number of spline knots; ignored unless
        ``fitType`` indicates a polynomial or spline.
    statControl : `lsst.afw.math.StatisticsControl`
        Statistics control object.  In particular, we pay attention to numSigmaClip
    overscanIsInt : `bool`
        Treat the overscan region as consisting of integers, even if it's been
        converted to float.  E.g. handle ties properly.

    Returns
    -------
    result : `lsst.pipe.base.Struct`
        Result struct with components:

        - ``imageFit``: Value(s) removed from image (scalar or
            `lsst.afw.image.Image`)
        - ``overscanFit``: Value(s) removed from overscan (scalar or
            `lsst.afw.image.Image`)
        - ``overscanImage``: Overscan corrected overscan region
            (`lsst.afw.image.Image`)
    Raises
    ------
    pexExcept.Exception
        Raised if ``fitType`` is not an allowed value.

    Notes
    -----
    The ``ampMaskedImage`` and ``overscanImage`` are modified, with the fit
    subtracted. Note that the ``overscanImage`` should not be a subimage of
    the ``ampMaskedImage``, to avoid being subtracted twice.

    Debug plots are available for the SPLINE fitTypes by setting the
    `debug.display` for `name` == "lsst.ip.isr.isrFunctions".  These
    plots show the scatter plot of the overscan data (collapsed along
    the perpendicular dimension) as a function of position on the CCD
    (normalized between +/-1).
    """
    ampImage = ampMaskedImage.getImage()
    if statControl is None:
        statControl = afwMath.StatisticsControl()

    numSigmaClip = statControl.getNumSigmaClip()

    if fitType in ('MEAN', 'MEANCLIP'):
        fitType = afwMath.stringToStatisticsProperty(fitType)
        offImage = afwMath.makeStatistics(overscanImage, fitType,
                                          statControl).getValue()
        overscanFit = offImage
    elif fitType in ('MEDIAN', ):
        if overscanIsInt:
            # we need an image with integer pixels to handle ties properly
            if hasattr(overscanImage, "image"):
                imageI = overscanImage.image.convertI()
                overscanImageI = afwImage.MaskedImageI(imageI,
                                                       overscanImage.mask,
                                                       overscanImage.variance)
            else:
                overscanImageI = overscanImage.convertI()
        else:
            overscanImageI = overscanImage

        fitType = afwMath.stringToStatisticsProperty(fitType)
        offImage = afwMath.makeStatistics(overscanImageI, fitType,
                                          statControl).getValue()
        overscanFit = offImage

        if overscanIsInt:
            del overscanImageI
    elif fitType in ('POLY', 'CHEB', 'LEG', 'NATURAL_SPLINE', 'CUBIC_SPLINE',
                     'AKIMA_SPLINE'):
        if hasattr(overscanImage, "getImage"):
            biasArray = overscanImage.getImage().getArray()
            biasArray = numpy.ma.masked_where(
                overscanImage.getMask().getArray() & statControl.getAndMask(),
                biasArray)
        else:
            biasArray = overscanImage.getArray()
        # Fit along the long axis, so collapse along each short row and fit the resulting array
        shortInd = numpy.argmin(biasArray.shape)
        if shortInd == 0:
            # Convert to some 'standard' representation to make things easier
            biasArray = numpy.transpose(biasArray)

        # Do a single round of clipping to weed out CR hits and signal leaking into the overscan
        percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
        medianBiasArr = percentiles[1]
        stdevBiasArr = 0.74 * (percentiles[2] - percentiles[0])  # robust stdev
        diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
        biasMaskedArr = numpy.ma.masked_where(
            diff > numSigmaClip * stdevBiasArr[:, numpy.newaxis], biasArray)
        collapsed = numpy.mean(biasMaskedArr, axis=1)
        if collapsed.mask.sum() > 0:
            collapsed.data[collapsed.mask] = numpy.mean(
                biasArray.data[collapsed.mask], axis=1)
        del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr

        if shortInd == 0:
            collapsed = numpy.transpose(collapsed)

        num = len(collapsed)
        indices = 2.0 * numpy.arange(num) / float(num) - 1.0

        if fitType in ('POLY', 'CHEB', 'LEG'):
            # A numpy polynomial
            poly = numpy.polynomial
            fitter, evaler = {
                "POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
                "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
                "LEG": (poly.legendre.legfit, poly.legendre.legval),
            }[fitType]

            coeffs = fitter(indices, collapsed, order)
            fitBiasArr = evaler(indices, coeffs)
        elif 'SPLINE' in fitType:
            # An afw interpolation
            numBins = order
            #
            # numpy.histogram needs a real array for the mask, but numpy.ma "optimises" the case
            # no-values-are-masked by replacing the mask array by a scalar, numpy.ma.nomask
            #
            # Issue DM-415
            #
            collapsedMask = collapsed.mask
            try:
                if collapsedMask == numpy.ma.nomask:
                    collapsedMask = numpy.array(
                        len(collapsed) * [numpy.ma.nomask])
            except ValueError:  # If collapsedMask is an array the test fails [needs .all()]
                pass

            numPerBin, binEdges = numpy.histogram(indices,
                                                  bins=numBins,
                                                  weights=1 -
                                                  collapsedMask.astype(int))
            # Binning is just a histogram, with weights equal to the values.
            # Use a similar trick to get the bin centers (this deals with different numbers per bin).
            with numpy.errstate(invalid="ignore"):  # suppress NAN warnings
                values = numpy.histogram(
                    indices,
                    bins=numBins,
                    weights=collapsed.data * ~collapsedMask)[0] / numPerBin
                binCenters = numpy.histogram(
                    indices, bins=numBins,
                    weights=indices * ~collapsedMask)[0] / numPerBin
                interp = afwMath.makeInterpolate(
                    binCenters.astype(float)[numPerBin > 0],
                    values.astype(float)[numPerBin > 0],
                    afwMath.stringToInterpStyle(fitType))
            fitBiasArr = numpy.array([interp.interpolate(i) for i in indices])

        import lsstDebug
        if lsstDebug.Info(__name__).display:
            import matplotlib.pyplot as plot
            figure = plot.figure(1)
            figure.clear()
            axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
            axes.plot(indices[~collapsedMask], collapsed[~collapsedMask], 'k+')
            if collapsedMask.sum() > 0:
                axes.plot(indices[collapsedMask],
                          collapsed.data[collapsedMask], 'b+')
            axes.plot(indices, fitBiasArr, 'r-')
            plot.xlabel("centered/scaled position along overscan region")
            plot.ylabel("pixel value/fit value")
            figure.show()
            prompt = "Press Enter or c to continue [chp]... "
            while True:
                ans = input(prompt).lower()
                if ans in (
                        "",
                        "c",
                ):
                    break
                if ans in ("p", ):
                    import pdb
                    pdb.set_trace()
                elif ans in ("h", ):
                    print("h[elp] c[ontinue] p[db]")
            plot.close()

        offImage = ampImage.Factory(ampImage.getDimensions())
        offArray = offImage.getArray()
        overscanFit = afwImage.ImageF(overscanImage.getDimensions())
        overscanArray = overscanFit.getArray()
        if shortInd == 1:
            offArray[:, :] = fitBiasArr[:, numpy.newaxis]
            overscanArray[:, :] = fitBiasArr[:, numpy.newaxis]
        else:
            offArray[:, :] = fitBiasArr[numpy.newaxis, :]
            overscanArray[:, :] = fitBiasArr[numpy.newaxis, :]

        # We don't trust any extrapolation: mask those pixels as SUSPECT
        # This will occur when the top and or bottom edges of the overscan
        # contain saturated values. The values will be extrapolated from
        # the surrounding pixels, but we cannot entirely trust the value of
        # the extrapolation, and will mark the image mask plane to flag the
        # image as such.
        mask = ampMaskedImage.getMask()
        maskArray = mask.getArray() if shortInd == 1 else mask.getArray(
        ).transpose()
        suspect = mask.getPlaneBitMask("SUSPECT")
        try:
            if collapsed.mask == numpy.ma.nomask:
                # There is no mask, so the whole array is fine
                pass
        except ValueError:  # If collapsed.mask is an array the test fails [needs .all()]
            for low in range(num):
                if not collapsed.mask[low]:
                    break
            if low > 0:
                maskArray[:low, :] |= suspect
            for high in range(1, num):
                if not collapsed.mask[-high]:
                    break
            if high > 1:
                maskArray[-high:, :] |= suspect

    else:
        raise pexExcept.Exception('%s : %s an invalid overscan type' %
                                  ("overscanCorrection", fitType))
    ampImage -= offImage
    overscanImage -= overscanFit
    return Struct(imageFit=offImage,
                  overscanFit=overscanFit,
                  overscanImage=overscanImage)