def setFocalPlanePos(self, pupilPos, focalPlanePos=None, beam=None): """Set the position of a spot on the focal plane. Compute new telescope, camera rotator and CBP positions and thus update beam info. Parameters ---------- pupilPos : pair of `float` Position of the specified beam on the :ref:`telescope pupil <lsst.cbp.pupil_position>` (x, y mm) focalPlanePos : pair of `float` (optional). :ref:`Focal plane position <lsst.cbp.focal_plane>` of the spot formed by the specified beam (x, y mm); defaults to (0, 0). beam : `int` or `str` (optional) Name or index of beam; defaults to self.maskInfo.defaultBeam. """ beam = self.maskInfo.asHoleName(beam) if focalPlanePos is None: focalPlanePos = Point2D(0, 0) else: focalPlanePos = Point2D(*focalPlanePos) focalFieldAngle = self._fieldAngleToFocalPlane.applyInverse( focalPlanePos) self.setFocalFieldAngle(pupilPos=pupilPos, focalFieldAngle=focalFieldAngle, beam=beam)
def test_Box2D_repr(self): from lsst.geom import Box2D, Point2D, Extent2D print(repr(Box2D())) self.assertEqual(eval(repr(Box2D())), Box2D()) self.assertEqual( eval(repr(Box2D(Point2D(1.0, 2.0), Extent2D(3.0, 4.0)))), Box2D(Point2D(1.0, 2.0), Extent2D(3.0, 4.0)))
def setUp(self): # Test geometry: # # -100,99 99,99 # +--------------------+ # |AAAAAAAAAACCCCCDDDDD| A == only in epoch A # |AAAAAAAAAACCCCCDDDDD| B == only in epoch B # |AAAAAAAAAACCCCCDDDDD| C == in both epoch A and epoch B # |AAAAAAAAAACCCCCDDDDD| D == in epoch A; in B's bbox but outside its ValidPolygon # |AAAAAAAAAACCCCCDDDDD| # | BBBBBBBBBB| All WCSs have the same CRVAL and CD. # | BBBBBBBBBB| # | BBBBBBBBBB| Coadd has CRPIX=(0, 0) # | BBBBBBBBBB| Epoch A has CRPIX=(0, -50) # | BBBBBBBBBB| Epoch B has CRPIX=(-50, 0) # +--------------------+ # -100,-100 99,-100 # self.rng = np.random.RandomState(50) crval = SpherePoint(45.0, 45.0, degrees) cdMatrix = makeCdMatrix(scale=5E-5 * degrees, flipX=True) self.wcsCoadd = makeSkyWcs(crpix=Point2D(0.0, 0.0), crval=crval, cdMatrix=cdMatrix) self.wcsA = makeSkyWcs(crpix=Point2D(0.0, -50.0), crval=crval, cdMatrix=cdMatrix) self.wcsB = makeSkyWcs(crpix=Point2D(-50.0, 0.0), crval=crval, cdMatrix=cdMatrix) self.bboxCoadd = Box2I(Point2I(-100, -100), Point2I(99, 99)) self.bboxA = Box2I(Point2I(-100, -50), Point2I(99, 49)) self.bboxB = Box2I(Point2I(-50, -100), Point2I(49, 99)) self.polygonA = None polygonD = Polygon(Box2D(Box2I(Point2I(0, 0), Point2I(49, 99)))) self.polygonB, = polygonD.symDifference(Polygon(Box2D(self.bboxB))) self.curveA = makeRandomTransmissionCurve(self.rng) self.curveB = makeRandomTransmissionCurve(self.rng) self.weightA = 0.6 self.weightB = 0.2 schema = ExposureTable.makeMinimalSchema() weightKey = schema.addField("weight", type=float, doc="relative weight of image in Coadd") catalog = ExposureCatalog(schema) recordA = catalog.addNew() recordA[weightKey] = self.weightA recordA.setWcs(self.wcsA) recordA.setValidPolygon(self.polygonA) recordA.setBBox(self.bboxA) recordA.setTransmissionCurve(self.curveA) recordB = catalog.addNew() recordB[weightKey] = self.weightB recordB.setWcs(self.wcsB) recordB.setValidPolygon(self.polygonB) recordB.setBBox(self.bboxB) recordB.setTransmissionCurve(self.curveB) self.curveCoadd = makeCoaddTransmissionCurve(self.wcsCoadd, catalog)
def testFixed(self): pos1 = Point2D(1.0, 1.0) pos2 = Point2D(-1.0, -1.0) img1 = self.fixedPsf.computeKernelImage(pos1) # Although _doComputeKernelImage would return a different image here due # do the difference between pos1 and pos2, for the fixed Psf, the # caching mechanism intercepts instead and _doComputeKernelImage is # never called with position=pos2. So img1 == img2. img2 = self.fixedPsf.computeKernelImage(pos2) self.assertFloatsEqual(img1.array, img2.array)
def setPupilFieldAngle(self, pupilPos, pupilFieldAngle=None, beam=None): """Set the pupil field angle and pupil position of a beam. Compute new telescope, camera rotator and CBP positions and thus update beam info. This method is primarily intended for internal use, to support the other set methods. It is public so it can be unit-tested. Parameters ---------- pupilPos : pair of `float` Position of the specified beam on the :ref:`telescope pupil <lsst.cbp.pupil_position>` (x, y mm). pupilFieldAngle : pair of `float` (optional) Pupil field angle of specified beam (x, y rad); defaults to (0, 0). beam : `int` or `str` (optional) Name or index of beam; defaults to self.maskInfo.defaultBeam. """ beam = self.maskInfo.asHoleName(beam) if pupilFieldAngle is None: pupilFieldAngle = Point2D(0, 0) else: pupilFieldAngle = Point2D(*pupilFieldAngle) beamPosAtCtr = coordUtils.computeShiftedPlanePos( pupilPos, pupilFieldAngle, -self.config.telPupilOffset) beamVectorInCtrPupil = self._computeBeamVectorInCtrPupilFrame( beamPosAtCtr=beamPosAtCtr, pupilFieldAngle=pupilFieldAngle) cbpVectorInCtrPupil = self._computeCbpVectorInCtrPupilFrame( beamPosAtCtr=beamPosAtCtr, beamVectorInCtrPupil=beamVectorInCtrPupil) telAzAlt = coordUtils.computeAzAltFromBasePupil( vectorBase=self.config.cbpPosition, vectorPupil=cbpVectorInCtrPupil) beamVectorBase = coordUtils.convertVectorFromPupilToBase( vectorPupil=beamVectorInCtrPupil, pupilAzAlt=telAzAlt, ) beamFieldAngleCbp = self._getBeamCbpFieldAngle(beam) beamUnitVectorCbpPupil = coordUtils.fieldAngleToVector( beamFieldAngleCbp, self.config.cbpFlipX) cbpAzAlt = coordUtils.computeAzAltFromBasePupil( vectorBase=-beamVectorBase, vectorPupil=beamUnitVectorCbpPupil, ) self._telRot = self._computeCameraRotatorAngle(telAzAlt=telAzAlt, cbpAzAlt=cbpAzAlt) self._telAzAlt = telAzAlt self._cbpAzAlt = cbpAzAlt
def setUp(self): # Set up a Coadd with CoaddInputs tables that have blank filter # columns to be filled in by later test code. self.coadd = ExposureF(30, 90) # WCS is arbitrary, since it'll be the same for all images wcs = makeSkyWcs(crpix=Point2D(0, 0), crval=SpherePoint(45.0, 45.0, degrees), cdMatrix=makeCdMatrix(scale=0.17 * degrees)) self.coadd.setWcs(wcs) schema = ExposureCatalog.Table.makeMinimalSchema() self.filterKey = schema.addField("filter", type=str, doc="", size=16) weightKey = schema.addField("weight", type=float, doc="") # First input image covers the first 2/3, second covers the last 2/3, # so they overlap in the middle 1/3. inputs = ExposureCatalog(schema) self.input1 = inputs.addNew() self.input1.setId(1) self.input1.setBBox(Box2I(Point2I(0, 0), Point2I(29, 59))) self.input1.setWcs(wcs) self.input1.set(weightKey, 2.0) self.input2 = inputs.addNew() self.input2.setId(2) self.input2.setBBox(Box2I(Point2I(0, 30), Point2I(29, 89))) self.input2.setWcs(wcs) self.input2.set(weightKey, 3.0) # Use the same catalog for visits and CCDs since the algorithm we're # testing only cares about CCDs. self.coadd.getInfo().setCoaddInputs(CoaddInputs(inputs, inputs)) # Set up a catalog with centroids and a FilterFraction plugin. # We have one record in each region (first input only, both inputs, # second input only) schema = SourceCatalog.Table.makeMinimalSchema() centroidKey = Point2DKey.addFields(schema, "centroid", doc="position", unit="pixel") schema.getAliasMap().set("slot_Centroid", "centroid") self.plugin = FilterFractionPlugin( config=FilterFractionPlugin.ConfigClass(), schema=schema, name="subaru_FilterFraction", metadata=PropertyList()) catalog = SourceCatalog(schema) self.record1 = catalog.addNew() self.record1.set(centroidKey, Point2D(14.0, 14.0)) self.record12 = catalog.addNew() self.record12.set(centroidKey, Point2D(14.0, 44.0)) self.record2 = catalog.addNew() self.record2.set(centroidKey, Point2D(14.0, 74.0))
def getCrpix(self, metadata): """Get CRPIX from metadata using the LSST convention: 0-based in parent coordinates """ return Point2D( # zero-based, hence the - 1 metadata.getScalar("CRPIX1") + metadata.getScalar("CRVAL1A") - 1, metadata.getScalar("CRPIX2") + metadata.getScalar("CRVAL2A") - 1, )
def testMakeSimpleWcsMetadata(self): crpix = Point2D(111.1, 222.2) crval = SpherePoint(45.6 * degrees, 12.3 * degrees) scale = 1 * arcseconds for orientation in (0 * degrees, 21 * degrees): cdMatrix = makeCdMatrix(scale=scale, orientation=orientation) for projection in ("TAN", "STG"): metadata = makeSimpleWcsMetadata(crpix=crpix, crval=crval, cdMatrix=cdMatrix, projection=projection) desiredLength = 11 if orientation == 0 * degrees else 13 self.assertEqual(len(metadata.names()), desiredLength) self.assertEqual(metadata.getScalar("RADESYS"), "ICRS") self.assertFalse(metadata.exists("EQUINOX")) self.assertEqual(metadata.getScalar("CTYPE1"), "RA---" + projection) self.assertEqual(metadata.getScalar("CTYPE2"), "DEC--" + projection) for i in range(2): self.assertAlmostEqual(metadata.getScalar(f"CRPIX{i + 1}"), crpix[i] + 1) self.assertAlmostEqual(metadata.getScalar(f"CRVAL{i + 1}"), crval[i].asDegrees()) self.assertEqual(metadata.getScalar(f"CUNIT{i + 1}"), "deg") for i in range(2): for j in range(2): name = f"CD{i + 1}_{j + 1}" if cdMatrix[i, j] != 0: self.assertAlmostEqual(metadata.getScalar(name), cdMatrix[i, j]) else: self.assertFalse(metadata.exists(name))
def computePsfImage(self, position=None): """Get a multiband PSF image The PSF Kernel Image is computed for each band and combined into a (filter, y, x) array and stored as `self._psfImage`. The result is not cached, so if the same PSF is expected to be used multiple times it is a good idea to store the result in another variable. Parameters ---------- position: `Point2D` or `tuple` Coordinates to evaluate the PSF. If `position` is `None` then `Psf.getAveragePosition()` is used. Returns ------- self._psfImage: array The multiband PSF image. """ psfs = [] # Make the coordinates into a Point2D (if necessary) if not isinstance(position, Point2D) and position is not None: position = Point2D(position[0], position[1]) for single in self.singles: if position is None: psfs.append(single.getPsf().computeImage().array) else: psfs.append(single.getPsf().computeImage(position).array) psfs = np.array(psfs) psfImage = MultibandImage(self.filters, array=psfs) return psfImage
def testFilters(self): wavelengths = np.linspace(3000, 12000, 1000) point = Point2D(1000, -500) def check(curve, central, w1, w2): # check that throughput within w1 of central is strictly greater # than throughput outside w2 of central throughput = curve.sampleAt(point, wavelengths) mid = np.logical_and(wavelengths > central - w1, wavelengths < central + w1) outer = np.logical_or(wavelengths < central - w2, wavelengths > central + w2) self.assertGreater(throughput[mid].min(), throughput[outer].max()) for curves in makeTransmissionCurves.getFilterTransmission().values(): check(curves["NB0387"], 3870, 50, 100) check(curves["NB0816"], 8160, 50, 100) check(curves["NB0921"], 9210, 50, 100) check(curves["HSC-G"], 4730, 500, 1500) check(curves["HSC-R"], 6230, 500, 1500) check(curves["HSC-R2"], 6230, 500, 1500) check(curves["HSC-I"], 7750, 500, 1500) check(curves["HSC-I2"], 7750, 500, 1500) check(curves["HSC-Z"], 8923, 500, 1500) check(curves["HSC-Y"], 10030, 500, 1500)
def testMultiPlaneFitsReaders(self): """Run tests for MaskedImageFitsReader and ExposureFitsReader. """ metadata = PropertyList() metadata.add("FIVE", 5) metadata.add("SIX", 6.0) wcs = makeSkyWcs(Point2D(2.5, 3.75), SpherePoint(40.0 * degrees, 50.0 * degrees), np.array([[1E-5, 0.0], [0.0, -1E-5]])) defineFilter("test_readers_filter", lambdaEff=470.0) calib = PhotoCalib(2.5E4) psf = GaussianPsf(21, 21, 8.0) polygon = Polygon(Box2D(self.bbox)) apCorrMap = ApCorrMap() visitInfo = VisitInfo(exposureTime=5.0) transmissionCurve = TransmissionCurve.makeIdentity() coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(), ExposureTable.makeMinimalSchema()) detector = DetectorWrapper().detector record = coaddInputs.ccds.addNew() record.setWcs(wcs) record.setPhotoCalib(calib) record.setPsf(psf) record.setValidPolygon(polygon) record.setApCorrMap(apCorrMap) record.setVisitInfo(visitInfo) record.setTransmissionCurve(transmissionCurve) record.setDetector(detector) for n, dtypeIn in enumerate(self.dtypes): with self.subTest(dtypeIn=dtypeIn): exposureIn = Exposure(self.bbox, dtype=dtypeIn) shape = exposureIn.image.array.shape exposureIn.image.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.mask.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.variance.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.setMetadata(metadata) exposureIn.setWcs(wcs) exposureIn.setFilter(Filter("test_readers_filter")) exposureIn.setFilterLabel( FilterLabel(physical="test_readers_filter")) exposureIn.setPhotoCalib(calib) exposureIn.setPsf(psf) exposureIn.getInfo().setValidPolygon(polygon) exposureIn.getInfo().setApCorrMap(apCorrMap) exposureIn.getInfo().setVisitInfo(visitInfo) exposureIn.getInfo().setTransmissionCurve(transmissionCurve) exposureIn.getInfo().setCoaddInputs(coaddInputs) exposureIn.setDetector(detector) with lsst.utils.tests.getTempFilePath(".fits") as fileName: exposureIn.writeFits(fileName) self.checkMaskedImageFitsReader(exposureIn, fileName, self.dtypes[n:]) self.checkExposureFitsReader(exposureIn, fileName, self.dtypes[n:])
def _createSkyWcsFromMetadata(self): # We need to know which direction the chip is "flipped" in order to # make a sensible WCS from the header metadata. wcs = makeSkyWcs(self.metadata, strip=True) dimensions = bboxFromMetadata(self.metadata).getDimensions() center = Point2D(dimensions/2.0) return wcs #makeFlippedWcs(wcs, self.FLIP_LR, self.FLIP_TB, center)
def makeWcs(self, crvalOffset, affMatrix): """Apply a shift and affine transform to the WCS internal to this class. A new SkyWcs with these transforms applied is returns. Parameters ---------- crval_shift : `numpy.ndarray`, (2,) Shift in radians to apply to the Wcs origin/crvals. aff_matrix : 'numpy.ndarray', (3, 3) Affine matrix to apply to the mapping/transform to add to the WCS. Returns ------- outputWcs : `lsst.afw.geom.SkyWcs` Wcs with a final shift and affine transform applied. """ # Create a WCS that only maps from IWC to Sky with the shifted # Sky origin position. This is simply the final undistorted tangent # plane to sky. The PIXELS to SKY map will be become our IWC to SKY # map and gives us our final shift position. iwcsToSkyWcs = makeSkyWcs( Point2D(0., 0.), self.origin.offset(crvalOffset[0] * degrees, crvalOffset[1] * arcseconds), np.array([[1., 0.], [0., 1.]])) iwcToSkyMap = iwcsToSkyWcs.getFrameDict().getMapping("PIXELS", "SKY") # Append a simple affine Matrix transform to the current to the # second to last frame mapping. e.g. the one just before IWC to SKY. newMapping = self.lastMapBeforeSky.then(astshim.MatrixMap(affMatrix)) # Create a new frame dict starting from the input_sky_wcs's first # frame. Append the correct mapping created above and our new on # sky location. outputFrameDict = astshim.FrameDict( self.frameDict.getFrame(self.frameMin)) for frameIdx in self.frameIdxs: if frameIdx == self.mapFrom: outputFrameDict.addFrame( self.mapFrom, newMapping, self.frameDict.getFrame(self.mapTo)) elif frameIdx >= self.mapTo: continue else: outputFrameDict.addFrame( frameIdx, self.frameDict.getMapping(frameIdx, frameIdx + 1), self.frameDict.getFrame(frameIdx + 1)) # Append the final sky frame to the frame dict. outputFrameDict.addFrame( self.frameMax - 1, iwcToSkyMap, iwcsToSkyWcs.getFrameDict().getFrame("SKY")) return SkyWcs(outputFrameDict)
def __init__(self, cameraGeom, name, holePos, isOnPupil, isOnFocalPlane, focalPlanePos, pupilPos, focalFieldAngle, pupilFieldAngle): self._cameraGeom = cameraGeom self.name = name self.holePos = holePos self.isOnPupil = isOnPupil self.isOnFocalPlane = isOnFocalPlane self.focalPlanePos = Point2D(*focalPlanePos) self.focalFieldAngle = Point2D(*focalFieldAngle) self.pupilFieldAngle = Point2D(*pupilFieldAngle) self.pupilPos = Point2D(*pupilPos) fieldAngleToFocalPlane = self._cameraGeom.getTransform( FIELD_ANGLE, FOCAL_PLANE) self.focalPlanePos = fieldAngleToFocalPlane.applyForward( self.focalFieldAngle) self._isOnDetector = None self._detector = None
def add_psf_image(exposure, x, y, flux): psfImg = exposure.getPsf().computeImage(Point2D(x, y)) psfImg *= flux subim = afwImage.ImageF(exposure.getMaskedImage().getImage(), psfImg.getBBox(), afwImage.LOCAL) subim += psfImg.convertF()
def testOptics(self): wavelengths = np.linspace(4000, 10000, 100) point = Point2D(1000, -500) for curve in makeTransmissionCurves.getOpticsTransmission().values(): if curve is None: continue throughputs = curve.sampleAt(point, wavelengths) self.assertTrue((throughputs > 0.70).all())
def checkExposureFitsReader(self, exposureIn, fileName, dtypesOut): """Test ExposureFitsReader. Parameters ---------- exposureIn : `Exposure` Object originally saved, to compare against. fileName : `str` Name of the file the reader is reading. dtypesOut : sequence of `numpy.dype` Compatible image pixel types to try to read in. """ reader = ExposureFitsReader(fileName) self.assertIn('EXPINFO_V', reader.readMetadata().toDict(), "metadata is automatically versioned") reader.readMetadata().remove('EXPINFO_V') self.assertEqual(exposureIn.getMetadata().toDict(), reader.readMetadata().toDict()) self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(), reader.readWcs(), self.bbox, maxDiffPix=0, maxDiffSky=0 * degrees) self.assertEqual(exposureIn.getFilter(), reader.readFilter()) self.assertEqual(exposureIn.getPhotoCalib(), reader.readPhotoCalib()) self.assertImagesEqual(exposureIn.getPsf().computeImage(), reader.readPsf().computeImage()) self.assertEqual(exposureIn.getInfo().getValidPolygon(), reader.readValidPolygon()) self.assertCountEqual(exposureIn.getInfo().getApCorrMap(), reader.readApCorrMap()) self.assertEqual(exposureIn.getInfo().getVisitInfo().getExposureTime(), reader.readVisitInfo().getExposureTime()) point = Point2D(2.3, 3.1) wavelengths = np.linspace(4000, 5000, 5) self.assertFloatsEqual( exposureIn.getInfo().getTransmissionCurve().sampleAt( point, wavelengths), reader.readTransmissionCurve().sampleAt(point, wavelengths)) # Because we persisted the same instances, we should get back the same # instances for *archive* components, and hence equality comparisons # should work even if it just amounts to C++ pointer equality. record = reader.readCoaddInputs().ccds[0] self.assertEqual(record.getWcs(), reader.readWcs()) self.assertEqual(record.getPsf(), reader.readPsf()) self.assertEqual(record.getValidPolygon(), reader.readValidPolygon()) self.assertEqual(record.getApCorrMap(), reader.readApCorrMap()) self.assertEqual(record.getPhotoCalib(), reader.readPhotoCalib()) self.assertEqual(record.getDetector(), reader.readDetector()) self.checkMultiPlaneReader( reader, exposureIn, fileName, dtypesOut, compare=lambda a, b: self.assertMaskedImagesEqual( a.maskedImage, b.maskedImage))
def computeHolePositions(detectorNames, detectorPositions, cameraGeom, cbpFlipX, cbpFocalLength): """Compute hole positions for a CBP mask. Given the desired locations of one or more spots on each detector, and assuming the telescope and CBP are pointing directly at each other, compute hole positions for a CBP mask. Parameters ---------- detectorNames : `iterable` of `str`, or None, List of detector names; if None, use all detectors in ``cameraGeom``, sorted by name. detectorPositions : `iterable` of pair of `float` Detector x, y positions (pixels). Note that the center of a 1000x1000 pixel detector is (499.5, 499.5) cameraGeom : `lsst.afw.cameraGeom.Camera` Camera geometry. cbpFlipX : `bool` Is the CBP focal plane flipped? Returns ------- holePositions : `list` of `tuple` of pair of `float` CBP mask x, y hole positions mm. Notes ----- This code assumes that all detectors have approximately the same dimensions and orientation. This restriction should suffice for LSST because the two kinds of CCDs it uses have very similar dimensions. However, it will not do for HSC because that has very rectangular CCDs and some are 90 degrees from the others. """ holePositions = [] pixelPosList = [Point2D(*val) for val in detectorPositions] if detectorNames is None: detectorNames = sorted(list(cameraGeom.getNameIter())) for detectorName in detectorNames: detector = cameraGeom[detectorName] pixelSys = detector.makeCameraSys(PIXELS) pixelsToFieldAngle = cameraGeom.getTransform(pixelSys, FIELD_ANGLE) telFieldAngleList = pixelsToFieldAngle.applyForward(pixelPosList) for telFieldAngle in telFieldAngleList: # Compute hole positions for when the telescope and CBP # are pointing at each other; # thus CBP pupil vector = telescope pupil vector with z negated. # This is simply a shortcut for setting telAzAlt and cbpAzAlt, # then transforming vectors from tel pupil to base, # then from base to CBP pupil. telVector = fieldAngleToVector(telFieldAngle, False) cbpVector = (telVector[0], telVector[1], -telVector[2]) cbpFieldAngle = vectorToFieldAngle(cbpVector, cbpFlipX) holePos = tuple( math.tan(ang) * cbpFocalLength for ang in cbpFieldAngle) holePositions.append(holePos) return holePositions
def testAtmosphere(self): wavelengths = np.linspace(4000, 12000, 100) point = Point2D(1000, -500) for curve in makeTransmissionCurves.getAtmosphereTransmission().values(): if curve is None: continue throughputs = curve.sampleAt(point, wavelengths) ohAbsorption = np.logical_and(wavelengths > 11315, wavelengths < 11397) midR = np.logical_and(wavelengths > 6000, wavelengths < 6300) self.assertTrue((throughputs[ohAbsorption] < throughputs[midR]).all())
def detectorPos(self): """The position of the spot on the detector, or (nan, nan) if `isOnDetector` is False. """ if not self.isOnDetector: return Point2D(np.nan, np.nan) pixelSys = self._detector.makeCameraSys(PIXELS) focalPlaneToPixels = self._cameraGeom.getTransform( FOCAL_PLANE, pixelSys) return focalPlaneToPixels.applyForward(self.focalPlanePos)
def testVectorization(self): x1 = np.random.randn(4, 3) y1 = np.random.randn(4, 3) x2, y2 = self.transform(x1, y1) self.assertEqual(x1.shape, x2.shape) self.assertEqual(x2.shape, y2.shape) for i in range(4): for j in range(3): x3, y3 = self.transform(Point2D(x1[i, j], y1[i, j])) self.assertAlmostEqual(x3, x2[i, j], 15) self.assertAlmostEqual(y3, y2[i, j], 15)
def testSensors(self): wavelengths = np.linspace(4000, 12000, 100) point = Point2D(200, 10) for sensors in makeTransmissionCurves.getSensorTransmission().values(): for i in range(112): curve = sensors[i] throughputs = curve.sampleAt(point, wavelengths) siliconTransparent = wavelengths > 11000 self.assertTrue((throughputs[siliconTransparent] < 0.01).all()) midR = np.logical_and(wavelengths > 6000, wavelengths < 6300) self.assertTrue((throughputs[midR] > 0.9).all())
def make_dm_wcs(galsim_wcs): """ convert galsim wcs to stack wcs Parameters ---------- galsim_wcs: galsim WCS Should be TAN or TAN-SIP Returns ------- DM Stack sky wcs """ if galsim_wcs.wcs_type == 'TAN': crpix = galsim_wcs.crpix # DM uses 0 offset, galsim uses FITS 1 offset stack_crpix = Point2D(crpix[0] - 1, crpix[1] - 1) cd_matrix = galsim_wcs.cd crval = geom.SpherePoint( galsim_wcs.center.ra / coord.radians, galsim_wcs.center.dec / coord.radians, geom.radians, ) stack_wcs = makeSkyWcs( crpix=stack_crpix, crval=crval, cdMatrix=cd_matrix, ) elif galsim_wcs.wcs_type == 'TAN-SIP': # No currently supported # this works with the 1-offset assumption from galsim # # this is not used if the lower bounds are 1, but the extra keywords # GS_{X,Y}MIN are set which we will remove below fake_bounds = galsim.BoundsI(1, 10, 1, 10) hdr = {} galsim_wcs.writeToFitsHeader(hdr, fake_bounds) del hdr["GS_XMIN"] del hdr["GS_YMIN"] metadata = PropertyList() for key, value in hdr.items(): metadata.set(key, value) stack_wcs = makeSkyWcs(metadata) return stack_wcs
def _computeCameraRotatorAngle(self, telAzAlt, cbpAzAlt): """Compute the internal camera rotator angle needed for a given telescope and CBP pointing. Parameters ---------- telAzAlt : `lsst.geom.SpherePoint` Telescope internal azimuth and altitude. cbpAzAlt : `lsst.geom.SpherePoint` CBP internal azimuth and altitude. Returns ------- rotatorAangle : `lsst.geom.Angle` Internal camera rotator angle. """ # Compute focal plane position, ignoring self._telRot, # for two holes separated by x in the CBP equidistant from the center. # Compute the angle that would make the spots line up # with the x axis in the focal plane. ctrHolePos = Point2D(0, 0) holeDelta = Extent2D(*coordUtils.getFlippedPos( self._holeDelta, flipX=self.config.cbpFlipX)) holePos1 = ctrHolePos - holeDelta holePos2 = ctrHolePos + holeDelta pupilUnitVector1 = self._computeTelPupilUnitVectorFromHolePos( holePos1, telAzAlt=telAzAlt, cbpAzAlt=cbpAzAlt) pupilUnitVector2 = self._computeTelPupilUnitVectorFromHolePos( holePos2, telAzAlt=telAzAlt, cbpAzAlt=cbpAzAlt) # Rotation is done in a right-handed system, regardless of telFlipX pupilFieldAngle1 = coordUtils.vectorToFieldAngle(pupilUnitVector1, flipX=False) pupilFieldAngle2 = coordUtils.vectorToFieldAngle(pupilUnitVector2, flipX=False) focalPlane1 = self._fieldAngleToFocalPlane.applyForward( Point2D(*pupilFieldAngle1)) focalPlane2 = self._fieldAngleToFocalPlane.applyForward( Point2D(*pupilFieldAngle2)) deltaFocalPlane = np.subtract(focalPlane2, focalPlane1) return -math.atan2(deltaFocalPlane[1], deltaFocalPlane[0]) * radians
def interp_tester(self, curve_class, args, detector): curve = curve_class(*args) w = 3500*u.angstrom xs = np.linspace(0, 1023, 33) ys = np.linspace(0, 1023, 33) val_map = {'A': 0.9329662, 'B': 0.7463730} # Does interpolation work for x, y in zip(xs, ys): point = Point2D(x, y) if detector: amp = cgUtils.findAmp(detector, Point2I(point)) value = val_map[amp.getName()] else: value = 0.9329662 interp_val = curve.evaluate(detector, point, w) self.assertAlmostEqual(interp_val.value, value, places=5) self.assertEqual(interp_val.unit, u.percent) # Does interpolation work with arrays w_arr = np.linspace(320, 430, 70)*u.nm out_arr = curve.evaluate(detector, point, w_arr) self.assertEqual(len(w_arr), len(out_arr)) # Does interpolation with different units work as expected point = Point2D(500., 500.) val1 = curve.evaluate(detector, point, w) new_w = w.to(u.mm) val2 = curve.evaluate(detector, point, new_w) self.assertEqual(val1.value, val2.value) # Does out of band interpolation do something reasonable # Default is to clamp to 0 outside the bounds. w = 0.*u.angstrom interp_val = curve.evaluate(detector, point, w) self.assertEqual(interp_val, 0.*u.percent) # interpolation with non-quantity should raise with self.assertRaises(ValueError): interp_val = curve.evaluate(detector, point, w.value) # Does interpolation fail with non-length unit with self.assertRaises(ValueError): w = 0.*u.Kelvin interp_val = curve.evaluate(detector, point, w)
def extractCtorArgs(md): wcs = makeSkyWcs(makePropertyListFromDict(md)) kwds = { "pixelToIwc": getPixelToIntermediateWorldCoords(wcs), "bbox": Box2D(Box2I(Point2I(0, 0), Extent2I(md["NAXES1"], md["NAXES2"]))), "crpix": Point2D(md["CRPIX1"] - 1.0, md["CRPIX2"] - 1.0), # -1 for LSST vs. FITS conventions "cd": np.array([[md["CD1_1"], md["CD1_2"]], [md["CD2_1"], md["CD2_2"]]]), } return kwds
def _computePsfImage(self, position=None): """Get a multiband PSF image The PSF Kernel Image is computed for each band and combined into a (filter, y, x) array and stored as `self._psfImage`. The result is not cached, so if the same PSF is expected to be used multiple times it is a good idea to store the result in another variable. Note: this is a temporary fix during the deblender sprint. In the future this function will replace the current method in `afw.MultibandExposure.computePsfImage` (DM-19789). Parameters ---------- position : `Point2D` or `tuple` Coordinates to evaluate the PSF. If `position` is `None` then `Psf.getAveragePosition()` is used. Returns ------- self._psfImage: array The multiband PSF image. """ psfs = [] # Make the coordinates into a Point2D (if necessary) if not isinstance(position, Point2D) and position is not None: position = Point2D(position[0], position[1]) for bidx, single in enumerate(self.singles): try: if position is None: psf = single.getPsf().computeImage() psfs.append(psf) else: psf = single.getPsf().computeKernelImage(position) psfs.append(psf) except InvalidParameterError: # This band failed to compute the PSF due to incomplete data # at that location. This is unlikely to be a problem for Rubin, # however the edges of some HSC COSMOS fields contain incomplete # data in some bands, so we track this error to distinguish it # from unknown errors. msg = "Failed to compute PSF at {} in band {}" raise IncompleteDataError(msg.format(position, self.filters[bidx])) left = np.min([psf.getBBox().getMinX() for psf in psfs]) bottom = np.min([psf.getBBox().getMinY() for psf in psfs]) right = np.max([psf.getBBox().getMaxX() for psf in psfs]) top = np.max([psf.getBBox().getMaxY() for psf in psfs]) bbox = Box2I(Point2I(left, bottom), Point2I(right, top)) psfs = [afwImage.utils.projectImage(psf, bbox) for psf in psfs] psfImage = afwImage.MultibandImage.fromImages(self.filters, psfs) return psfImage
def setUp(self): xy0 = Point2I(12345, 67890) # xy0 for image dims = Extent2I(2345, 2345) # Dimensions of image box = Box2I(xy0, dims) # Bounding box of image sigma = 3.21 # PSF sigma buffer = 4.0 # Buffer for star centers around edge nSigmaForKernel = 5.0 # Number of PSF sigmas for kernel sky = 12345.6 # Sky level numStars = 100 # Number of stars noise = np.sqrt(sky) * np.pi * sigma**2 # Poisson noise per PSF faint = 1.0 * noise # Faintest level for star fluxes bright = 100.0 * noise # Brightest level for star fluxes starBox = Box2I(box) # Area on image in which we can put star centers starBox.grow(-int(buffer * sigma)) scale = 1.0e-5 * degrees # Pixel scale np.random.seed(12345) stars = [(xx, yy, ff, sigma) for xx, yy, ff in zip( np.random.uniform(starBox.getMinX(), starBox.getMaxX(), numStars), np.random.uniform(starBox.getMinY(), starBox.getMaxY(), numStars), np.linspace(faint, bright, numStars))] self.exposure = plantSources(box, 2 * int(nSigmaForKernel * sigma) + 1, sky, stars, True) self.exposure.setWcs( makeSkyWcs(crpix=Point2D(0, 0), crval=SpherePoint(0, 0, degrees), cdMatrix=makeCdMatrix(scale=scale))) # Make a large area of extra background; we should be robust against it # Unfortunately, some tuning is required here to get something challenging but not impossible: # * A very large box will cause failures because the "extra" and the "normal" are reversed. # * A small box will not be challenging because it's simple to clip out. # * A large value will cause failures because it produces large edges in background-subtrction that # broaden flux distributions. # * A small value will not be challenging because it has little effect. extraBox = Box2I(xy0 + Extent2I(345, 456), Extent2I(1234, 1234)) # Box for extra background extraValue = 0.5 * noise # Extra background value to add in self.exposure.image[extraBox, PARENT] += extraValue self.config = DynamicDetectionTask.ConfigClass() self.config.skyObjects.nSources = 300 self.config.reEstimateBackground = False self.config.doTempWideBackground = True self.config.thresholdType = "pixel_stdev" # Relative tolerance for tweak factor # Not sure why this isn't smaller; maybe due to use of Poisson instead of Gaussian noise? self.rtol = 0.1
def checkOrientation(self, maxDiff=50*arcseconds): """Check that the orientation of the focal plane is correct The definition of correct orientation is that two points to the left and right of the CBP center (by a buried delta) line up in the focal plane. We'll just use +/-1 for our delta """ ctrHolePos = Point2D(0, 0) dx = -1 if self.cco.config.cbpFlipX else 1 holePos1 = ctrHolePos[0] + dx, ctrHolePos[1] holePos2 = ctrHolePos[0] + dx, ctrHolePos[1] beamInfo1 = self.cco.getBeamInfo(beam="virtual1", holePos=holePos1) beamInfo2 = self.cco.getBeamInfo(beam="virtual2", holePos=holePos2) deltaFocalPlane = np.subtract(beamInfo2.focalPlanePos, beamInfo1.focalPlanePos) orientError = -math.atan2(deltaFocalPlane[1], deltaFocalPlane[0])*radians self.assertAnglesAlmostEqual(orientError, 0*radians)
def __init__(self, name, defaultHole, holePositions, holeNames=None): self.name = name if holeNames is not None: if len(holePositions) != len(holeNames): raise ValueError( "Number of hole positions = {} != Number of hole names = {}" .format(len(holePositions), len(holeNames))) else: holeNames = [str(i) for i in range(len(holePositions))] self._holePosDict = OrderedDict() for holeName, holePos in zip(holeNames, holePositions): self._holePosDict[holeName] = Point2D(*holePos) # parse "defaultBeam" after "holes", so we can check the default if defaultHole is None: raise ValueError("defaultHole cannot be None") self.defaultBeam = self.asHoleName(defaultHole)