def refLinearize(image, detector, table): """!Basic implementation of lookup table based non-linearity correction @param[in,out] image image to correct in place (an lsst.afw.image.Image of some type) @param[in] detector detector info (an lsst.afw.cameraGeom.Detector) @param[in] table lookup table: a 2D array of values of the same type as image; - one row for each row index (value of coef[0] in the amp info catalog) - one column for each image value @return the number of pixels whose values were out of range of the lookup table """ ampInfoCat = detector.getAmplifiers() numOutOfRange = 0 for ampInfo in ampInfoCat: bbox = ampInfo.getBBox() rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2] rowInd = int(rowInd) tableRow = table[rowInd, :] imView = image.Factory(image, bbox) numOutOfRange += applyLookupTable(imView, tableRow, colIndOffset) return numOutOfRange
def testBasics(self): """!Test basic functionality of applyLookupTable """ bbox = afwGeom.Box2I(afwGeom.Point2I(-31, 22), afwGeom.Extent2I(100, 85)) imMin = -5 imMax = 2500 tableLen = 2000 tableSigma = 55 for indOffset in (0, -50, 234): for imageClass in (afwImage.ImageF, afwImage.ImageD): inImage = makeRampImage(bbox=bbox, start=imMin, stop=imMax, imageClass=imageClass) table = np.random.normal(scale=tableSigma, size=tableLen) table = np.array(table, dtype=inImage.getArray().dtype) refImage = imageClass(inImage, True) refNumBad = referenceApply(image=refImage, table=table, indOffset=indOffset) measImage = imageClass(inImage, True) measNumBad = applyLookupTable(measImage, table, indOffset) self.assertEqual(refNumBad, measNumBad) self.assertImagesAlmostEqual(refImage, measImage)
def testKnown(self): """Test that a given image and lookup table produce the known answer Apply a negative ramp table to a positive ramp image to get a flat image, but have one value out of range at each end, to offset each end point by one """ # generate a small ramp image with ascending integer values # starting at some small negative value going positive bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(3, 4)) numPix = bbox.getWidth() * bbox.getHeight() start = -3 stop = start + numPix - 1 im = makeRampImage(bbox=bbox, start=start, stop=stop, imageClass=afwImage.ImageF) # generate a ramp lookup table with descending integer values, # with a range offset by a small arbitrary value from the image ramp # make it two elements too short so we can have one value out of range # at each end numOutOfRangePerEnd = 1 numOutOfRange = 2 * numOutOfRangePerEnd tableOffset = -2 table = np.linspace(start=stop + tableOffset, stop=numOutOfRange + start + tableOffset, num=numPix - numOutOfRange) table = np.array(table, dtype=im.getArray().dtype) # apply the table with the first and last image value out of range by # one. indOffset = -(start + numOutOfRangePerEnd) measNumOutOfRange = applyLookupTable(im, table, indOffset) self.assertEqual(numOutOfRange, measNumOutOfRange) # at this point the image should all have the same value # except the first point will be one less and the last one more imArr = im.getArray() desVal = start + numOutOfRangePerEnd + table[0] desImArr = np.zeros(numPix, dtype=im.getArray().dtype) desImArr[:] = desVal desImArr[0] -= 1 desImArr[-1] += 1 desImArr.shape = imArr.shape self.assertFloatsAlmostEqual(desImArr, imArr)
def refLinearize(image, detector, table): """!Basic implementation of lookup table based non-linearity correction @param[in,out] image image to correct in place (an lsst.afw.image.Image of some type) @param[in] detector detector info (an lsst.afw.cameraGeom.Detector) @param[in] table lookup table: a 2D array of values of the same type as image; - one row for each row index (value of coef[0] in the amp info catalog) - one column for each image value @return the number of pixels whose values were out of range of the lookup table """ ampInfoCat = detector.getAmpInfoCatalog() numOutOfRange = 0 for ampInfo in ampInfoCat: bbox = ampInfo.getBBox() rowInd, colIndOffset = ampInfo.getLinearityCoeffs()[0:2] rowInd = int(rowInd) tableRow = table[rowInd, :] imView = image.Factory(image, bbox) numOutOfRange += applyLookupTable(imView, tableRow, colIndOffset) return numOutOfRange
def testKnown(self): """Test that a given image and lookup table produce the known answer Apply a negative ramp table to a positive ramp image to get a flat image, but have one value out of range at each end, to offset each end point by one """ # generate a small ramp image with ascending integer values # starting at some small negative value going positive bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(3, 4)) numPix = bbox.getWidth()*bbox.getHeight() start = -3 stop = start + numPix - 1 im = makeRampImage(bbox=bbox, start=start, stop=stop, imageClass=afwImage.ImageF) # generate a ramp lookup table with descending integer values, # with a range offset by a small arbitrary value from the image ramp # make it two elements too short so we can have one value out of range at each end numOutOfRangePerEnd = 1 numOutOfRange = 2*numOutOfRangePerEnd tableOffset = -2 table = np.linspace( start=stop + tableOffset, stop=numOutOfRange + start + tableOffset, num=numPix - numOutOfRange) table = np.array(table, dtype=im.getArray().dtype) # apply the table with the first and last image value out of range by one indOffset = -(start + numOutOfRangePerEnd) measNumOutOfRange = applyLookupTable(im, table, indOffset) self.assertEqual(numOutOfRange, measNumOutOfRange) # at this point the image should all have the same value # except the first point will be one less and the last one more imArr = im.getArray() desVal = start + numOutOfRangePerEnd + table[0] desImArr = np.zeros(numPix, dtype=im.getArray().dtype) desImArr[:] = desVal desImArr[0] -= 1 desImArr[-1] += 1 desImArr.shape = imArr.shape self.assertFloatsAlmostEqual(desImArr, imArr)
def referenceImage(image, detector, linearityType, inputData, table=None): """Generate a reference linearization. Parameters ---------- image: `lsst.afw.image.Image` Image to linearize. detector: `lsst.afw.cameraGeom.Detector` Detector this image is from. linearityType: `str` Type of linearity to apply. inputData: `numpy.array` An array of values for the linearity correction. table: `numpy.array`, optional An optional lookup table to use. Returns ------- outImage: `lsst.afw.image.Image` The output linearized image. numOutOfRange: `int` The number of values that could not be linearized. Raises ------ RuntimeError : Raised if an invalid linearityType is supplied. """ numOutOfRange = 0 for ampIdx, amp in enumerate(detector.getAmplifiers()): ampIdx = (ampIdx // 3, ampIdx % 3) bbox = amp.getBBox() imageView = image.Factory(image, bbox) if linearityType == 'Squared': sqCoeff = inputData[ampIdx] array = imageView.getArray() array[:] = array + sqCoeff * array**2 elif linearityType == 'LookupTable': rowInd, colIndOffset = inputData[ampIdx] rowInd = int(rowInd) tableRow = table[rowInd, :] numOutOfRange += applyLookupTable(imageView, tableRow, colIndOffset) elif linearityType == 'Polynomial': coeffs = inputData[ampIdx] array = imageView.getArray() summation = np.zeros_like(array) for index, coeff in enumerate(coeffs): summation += coeff * np.power(array, (index + 2)) array += summation elif linearityType == 'Spline': centers, values = np.split(inputData, 2) # This uses the full data interp = afwMath.makeInterpolate( centers.tolist(), values.tolist(), afwMath.stringToInterpStyle('AKIMA_SPLINE')) array = imageView.getArray() delta = interp.interpolate(array.flatten()) array -= np.array(delta).reshape(array.shape) else: raise RuntimeError(f"Unknown linearity: {linearityType}") return image, numOutOfRange