def bypass_instcal(self, datasetType, pythonType, butlerLocation, dataId): # Workaround until I can access the butler instcalMap = self.map_instcal(dataId) dqmaskMap = self.map_dqmask(dataId) wtmapMap = self.map_wtmap(dataId) instcalType = getattr(afwImage, instcalMap.getPythonType().split(".")[-1]) dqmaskType = getattr(afwImage, dqmaskMap.getPythonType().split(".")[-1]) wtmapType = getattr(afwImage, wtmapMap.getPythonType().split(".")[-1]) instcal = instcalType(instcalMap.getLocationsWithRoot()[0]) dqmask = dqmaskType(dqmaskMap.getLocationsWithRoot()[0]) wtmap = wtmapType(wtmapMap.getLocationsWithRoot()[0]) mask = self.translate_dqmask(dqmask) variance = self.translate_wtmap(wtmap) mi = afwImage.MaskedImageF(afwImage.ImageF(instcal.getImage()), mask, variance) md = readMetadata(instcalMap.getLocationsWithRoot()[0]) wcs = makeSkyWcs(md, strip=True) exp = afwImage.ExposureF(mi, wcs) exp.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint( 10**(0.4 * md.getScalar("MAGZERO")), 0)) visitInfo = self.makeRawVisitInfo(md=md) exp.getInfo().setVisitInfo(visitInfo) for kw in ('LTV1', 'LTV2'): md.remove(kw) exp.setMetadata(md) return exp
def writeFcr(self, dataRefList): self.log.info("Write Fcr ...") M_LN10 = math.log(10) dmag = list() for m in self.matchVec: if (m.good == True and m.mag != -9999 and m.jstar != -1 and m.mag0 != -9999 and m.mag_cat != -9999): mag = m.mag mag_cat = m.mag_cat exp_cor = -2.5*math.log10(self.fexp[m.iexp]) chip_cor = -2.5*math.log10(self.fchip[m.ichip]) gain_cor = self.ffpSet[m.iexp].eval(m.u, m.v) mag_cor = mag + exp_cor + chip_cor + gain_cor dmag.append(mag_cor - mag_cat) std, mean, n = mosaicUtils.clippedStd(numpy.array(dmag), 2.1) for dataRef in dataRefList: iexp = dataRef.dataId["visit"] ichip = dataRef.dataId["ccd"] try: x0 = self.coeffSet[iexp].x0 y0 = self.coeffSet[iexp].y0 except: x0 = 0.0 y0 = 0.0 newP = measMosaic.convertFluxFitParams(measMosaic.FluxFitParams(self.ffpSet[iexp]), self.ccdSet[ichip], x0, y0) metadata = measMosaic.metadataFromFluxFitParams(newP) exp = afwImage.ExposureI(0,0) exp.getMetadata().combine(metadata) scale = self.fexp[iexp]*self.fchip[ichip] constantPhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1.0/scale, 1.0/scale*std*M_LN10*0.4) exp.setPhotoCalib(constantPhotoCalib) try: dataRef.put(exp, "fcr") except Exception as e: print("failed to write fcr: %s" % (e)) # Write the flux fit (including Jacobian) as a PhotoCalib for # future compatibility with jointcal. This is redundant with # the above, and should eventually supercede it. detector = dataRef.get("camera")[dataRef.dataId["ccd"]] nQuarter = detector.getOrientation().getNQuarter() bbox = detector.getBBox() try: # Reading the Wcs we just wrote obviously isn't efficient, but # it should be in the noise of the overall runtime and it # saves us from doing a bunch of refactoring in a fragile # package with no tests. wcs = dataRef.get("jointcal_wcs") except Exception as e: print("failed to read Wcs for PhotoCalib: %s" % (e)) continue bf = measMosaic.FluxFitBoundedField(bbox, newP, wcs, zeroPoint=constantPhotoCalib.getInstFluxAtZeroMagnitude(), nQuarter=nQuarter) varyingPhotoCalib = afwImage.PhotoCalib(constantPhotoCalib.getCalibrationMean(), constantPhotoCalib.getCalibrationErr(), bf, isConstant=False) dataRef.put(varyingPhotoCalib, "jointcal_photoCalib")
def makeHSCExposure(self, galData, psfData, pixScale, variance): ny, nx = galData.shape exposure = afwImg.ExposureF(nx, ny) exposure.getMaskedImage().getImage().getArray()[:, :] = galData exposure.getMaskedImage().getVariance().getArray()[:, :] = variance #Set the PSF ngridPsf = psfData.shape[0] psfLsst = afwImg.ImageF(ngridPsf, ngridPsf) psfLsst.getArray()[:, :] = psfData psfLsst = psfLsst.convertD() kernel = afwMath.FixedKernel(psfLsst) kernelPSF = meaAlg.KernelPsf(kernel) exposure.setPsf(kernelPSF) #prepare the wcs #Rotation cdelt = (pixScale * afwGeom.arcseconds) CD = afwGeom.makeCdMatrix(cdelt, afwGeom.Angle(0.)) #no rotation #wcs crval = afwGeom.SpherePoint(afwGeom.Angle(0., afwGeom.degrees), afwGeom.Angle(0., afwGeom.degrees)) #crval = afwCoord.IcrsCoord(0.*afwGeom.degrees, 0.*afwGeom.degrees) # hscpipe6 crpix = afwGeom.Point2D(0.0, 0.0) dataWcs = afwGeom.makeSkyWcs(crpix, crval, CD) exposure.setWcs(dataWcs) #prepare the frc dataCalib = afwImg.makePhotoCalibFromCalibZeroPoint(63095734448.0194) exposure.setPhotoCalib(dataCalib) return exposure
def __init__(self, *args, **kwargs): """Construct a ScaleZeroPointTask """ pipeBase.Task.__init__(self, *args, **kwargs) # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0) fluxMag0 = 10**(0.4 * self.config.zeroPoint) self._photoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)
def scaleFromFluxMag0(self, fluxMag0): """Compute the scale for the specified fluxMag0 This is a wrapper around scaleFromPhotoCalib, which see for more information @param[in] fluxMag0 @return a pipeBase.Struct containing: - scale, as described in scaleFromPhotoCalib. """ calib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0) return self.scaleFromPhotoCalib(calib)
def setUp(self): self.dataDir = os.path.join(os.path.split(__file__)[0], "data") # Check the values below against what was written by comparing with # the code in `afw/tests/data/makeTestExposure.py` nx = ny = 10 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny)) variance = afwImage.ImageF(np.ones((nx, ny), dtype='f')) mask = afwImage.MaskX(nx, ny) mask.array[5, 5] = 5 self.maskedImage = afwImage.MaskedImageF(image, mask, variance) self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4) self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
def setUp(self): self.dataDir = os.path.join(os.path.split(__file__)[0], "data") # Check the values below against what was written by comparing with # the code in `afw/tests/data/makeTestExposure.py` nx = ny = 10 image = afwImage.ImageF(np.arange(nx*ny, dtype='f').reshape(nx, ny)) variance = afwImage.ImageF(np.ones((nx, ny), dtype='f')) mask = afwImage.MaskX(nx, ny) mask.array[5, 5] = 5 self.maskedImage = afwImage.MaskedImageF(image, mask, variance) self.v0PhotoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(1e6, 2e4) self.v1PhotoCalib = afwImage.PhotoCalib(1e6, 2e4)
def setUp(self): '''Create two calibs, one with a valid zero-point and one without. Use these to create two UnitSystem objects. ''' self.mag2Flux = lambda m: 10.0**(m/2.5) self.flux2Mag = lambda f: 2.5*np.log10(f) photoCalibNoZero = afwImage.PhotoCalib() photoCalibWithZero = afwImage.makePhotoCalibFromCalibZeroPoint(self.mag2Flux(25)) scale = 0.2 * geom.arcseconds wcs = afwGeom.makeSkyWcs(crpix=geom.Point2D(), crval=geom.SpherePoint(45.0, 45.0, geom.degrees), cdMatrix=afwGeom.makeCdMatrix(scale=scale)) self.unitNoZero = measModel.UnitSystem(wcs, photoCalibNoZero) self.unitWithZero = measModel.UnitSystem(wcs, photoCalibWithZero)
def setUp(self): '''Create two calibs, one with a valid zero-point and one without. Use these to create two UnitSystem objects. ''' self.mag2Flux = lambda m: 10.0**(m/2.5) self.flux2Mag = lambda f: 2.5*np.log10(f) photoCalibNoZero = afwImage.PhotoCalib() photoCalibWithZero = afwImage.makePhotoCalibFromCalibZeroPoint(self.mag2Flux(25)) scale = 0.2 * afwGeom.arcseconds wcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(), crval=afwGeom.SpherePoint(45.0, 45.0, afwGeom.degrees), cdMatrix=afwGeom.makeCdMatrix(scale=scale)) self.unitNoZero = measModel.UnitSystem(wcs, photoCalibNoZero) self.unitWithZero = measModel.UnitSystem(wcs, photoCalibWithZero)
def converttsField(infile, filt, exptime=53.907456): """Extract data from a tsField table @param[in] infile path to tsField FITS file @param[in] filt index of filter in tsField FILTERS metadata entry @param[in] exptime exposure time (sec) @return a dict with the following entries: - photoCalib: an lsst.afw.PhotoCalib - gain: gain as a float - dateAvg: date of exposure at middle of exposure, as an lsst.daf.base.DateTime - exptime: exposure time (sec) - airmass: airmass """ ptr = fits.open(infile) if ptr[0].header['NFIELDS'] != 1: print("INVALID TSFIELD FILE") sys.exit(1) filts = ptr[0].header['FILTERS'].split() idx = filts.index(filt) mjdTaiStart = ptr[1].data.field('mjd')[0][idx] # MJD(TAI) when row 0 was read airmass = ptr[1].data.field("airmass")[0][idx] gain = float(ptr[1].data.field('gain')[0][idx]) # comes out as numpy.float32 aa = ptr[1].data.field('aa')[0][idx] # f0 = 10**(-0.4*aa) counts/second aaErr = ptr[1].data.field('aaErr')[0][idx] # Conversions dateAvg = dafBase.DateTime(mjdTaiStart + 0.5 * exptime / 3600 / 24) fluxMag0 = 10**(-0.4 * aa) * exptime dfluxMag0 = fluxMag0 * 0.4 * np.log(10.0) * aaErr photoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, dfluxMag0) ptr.close() return TsField( photoCalib=photoCalib, gain=gain, dateAvg=dateAvg, exptime=exptime, airmass=airmass, )
def applyMosaicResultsExposure(dataRef, calexp=None): """Update an Exposure with the Wcs, Calib, and flux scaling from meas_mosaic. If None, the calexp will be loaded from the dataRef. Otherwise it is updated in-place. This assumes that the mosaic solution exists; an exception will be raised in the event that it does not. """ if calexp is None: calexp = dataRef.get("calexp", immediate=True) nQuarter = calexp.getDetector().getOrientation().getNQuarter() dims = calexp.getDimensions() hscRun = mosaicUtils.checkHscStack(calexp.getMetadata()) # Need the dimensions in coordinates used by meas_mosaic which defines 0,0 as the # lower-left hand corner on the sky if hscRun is None: if nQuarter % 2 != 0: width, height = calexp.getDimensions() dims = afwGeom.Extent2I(height, width) # return results in meas_mosaic coordinate system mosaic = getMosaicResults(dataRef, dims) # rotate wcs back to LSST coordinate system if nQuarter % 4 != 0 and hscRun is None: import lsst.meas.astrom as measAstrom mosaic.wcs = measAstrom.rotateWcsPixelsBy90(mosaic.wcs, 4 - nQuarter, dims) calexp.setWcs(mosaic.wcs) fluxMag0 = mosaic.calib.getInstFluxAtZeroMagnitude() calexp.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)) mi = calexp.getMaskedImage() # rotate photometric correction to LSST coordiantes if nQuarter % 4 != 0 and hscRun is None: mosaic.fcor = afwMath.rotateImageBy90(mosaic.fcor, 4 - nQuarter) mi *= mosaic.fcor return Struct(exposure=calexp, mosaic=mosaic)
def makeLsstExposure(galData, psfData, pixScale, variance): """ make an LSST exposure object Parameters: galData (ndarray): array of galaxy image psfData (ndarray): array of PSF image pixScale (float): pixel scale variance (float): noise variance Returns: exposure: LSST exposure object """ if not with_lsst: raise ImportError('Do not have lsstpipe!') ny, nx = galData.shape exposure = afwImg.ExposureF(nx, ny) exposure.getMaskedImage().getImage().getArray()[:, :] = galData exposure.getMaskedImage().getVariance().getArray()[:, :] = variance #Set the PSF ngridPsf = psfData.shape[0] psfLsst = afwImg.ImageF(ngridPsf, ngridPsf) psfLsst.getArray()[:, :] = psfData psfLsst = psfLsst.convertD() kernel = afwMath.FixedKernel(psfLsst) kernelPSF = meaAlg.KernelPsf(kernel) exposure.setPsf(kernelPSF) #prepare the wcs #Rotation cdelt = (pixScale * afwGeom.arcseconds) CD = afwGeom.makeCdMatrix(cdelt, afwGeom.Angle(0.)) #no rotation #wcs crval = afwGeom.SpherePoint(afwGeom.Angle(0., afwGeom.degrees), afwGeom.Angle(0., afwGeom.degrees)) #crval = afwCoord.IcrsCoord(0.*afwGeom.degrees, 0.*afwGeom.degrees) # hscpipe6 crpix = afwGeom.Point2D(0.0, 0.0) dataWcs = afwGeom.makeSkyWcs(crpix, crval, CD) exposure.setWcs(dataWcs) #prepare the frc dataCalib = afwImg.makePhotoCalibFromCalibZeroPoint(63095734448.0194) exposure.setPhotoCalib(dataCalib) return exposure
def applyMosaicResultsExposure(dataRef, calexp=None): """Update an Exposure with the Wcs, Calib, and flux scaling from meas_mosaic. If None, the calexp will be loaded from the dataRef. Otherwise it is updated in-place. This assumes that the mosaic solution exists; an exception will be raised in the event that it does not. """ if calexp is None: calexp = dataRef.get("calexp", immediate=True) nQuarter = calexp.getDetector().getOrientation().getNQuarter() dims = calexp.getDimensions() hscRun = mosaicUtils.checkHscStack(calexp.getMetadata()) # Need the dimensions in coordinates used by meas_mosaic which defines 0,0 as the # lower-left hand corner on the sky if hscRun is None: if nQuarter%2 != 0: width, height = calexp.getDimensions() dims = afwGeom.Extent2I(height, width) # return results in meas_mosaic coordinate system mosaic = getMosaicResults(dataRef, dims) # rotate wcs back to LSST coordinate system if nQuarter%4 != 0 and hscRun is None: import lsst.meas.astrom as measAstrom mosaic.wcs = measAstrom.rotateWcsPixelsBy90(mosaic.wcs, 4 - nQuarter, dims) calexp.setWcs(mosaic.wcs) fluxMag0 = mosaic.calib.getInstFluxAtZeroMagnitude() calexp.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)) mi = calexp.getMaskedImage() # rotate photometric correction to LSST coordiantes if nQuarter%4 != 0 and hscRun is None: mosaic.fcor = afwMath.rotateImageBy90(mosaic.fcor, 4 - nQuarter) mi *= mosaic.fcor return Struct(exposure=calexp, mosaic=mosaic)
def writeFcr(self, butler, ccdIdList, ccdSet, filterName, fexp, fchip, ffpSet): for ccdId in ccdIdList: iexp, ichip = self.decodeCcdExposureId(ccdId) if ichip not in ccdSet: continue x0 = 0.0 y0 = 0.0 newP = measMosaic.convertFluxFitParams(measMosaic.FluxFitParams(ffpSet[iexp]), ccdSet[ichip], x0, y0) metadata = measMosaic.metadataFromFluxFitParams(newP) exp = afwImage.ExposureI(0,0) exp.getMetadata().combine(metadata) scale = fexp[iexp] * fchip[ichip] exp.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(1.0/scale)) exp.setFilter(afwImage.Filter(filterName)) try: butler.put(exp, 'fcr', {'visit': iexp, 'ccd': ichip}) except Exception as e: print("failed to write something: %s" % (e))
def writeFcr(self, butler, ccdIdList, ccdSet, filterName, fexp, fchip, ffpSet): for ccdId in ccdIdList: iexp, ichip = self.decodeCcdExposureId(ccdId) if ichip not in ccdSet: continue x0 = 0.0 y0 = 0.0 newP = measMosaic.convertFluxFitParams( measMosaic.FluxFitParams(ffpSet[iexp]), ccdSet[ichip], x0, y0) metadata = measMosaic.metadataFromFluxFitParams(newP) exp = afwImage.ExposureI(0, 0) exp.getMetadata().combine(metadata) scale = fexp[iexp] * fchip[ichip] exp.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint(1.0 / scale)) exp.setFilter(afwImage.Filter(filterName)) try: butler.put(exp, 'fcr', {'visit': iexp, 'ccd': ichip}) except Exception as e: print("failed to write something: %s" % (e))
def __init__(self, **kwargs): pipeBase.Task.__init__(self, **kwargs) self.cat = None self.footprints = [] self.calib = afwImage.makePhotoCalibFromCalibZeroPoint( 10**(0.4 * self.config.commonZp))
class MockCoaddTestData: """Generate repeatable simulated exposures with consistent metadata that are realistic enough to test the image coaddition algorithms. Notes ----- The simple GaussianPsf used by lsst.meas.algorithms.testUtils.plantSources will always return an average position of (0, 0). The bounding box of the exposures MUST include (0, 0), or else the PSF will not be valid and `AssembleCoaddTask` will fail with the error 'Could not find a valid average position for CoaddPsf'. Parameters ---------- shape : `lsst.geom.Extent2I`, optional Size of the bounding box of the exposures to be simulated, in pixels. offset : `lsst.geom.Point2I`, optional Pixel coordinate of the lower left corner of the bounding box. backgroundLevel : `float`, optional Background value added to all pixels in the simulated images. seed : `int`, optional Seed value to initialize the random number generator. nSrc : `int`, optional Number of sources to simulate. fluxRange : `float`, optional Range in flux amplitude of the simulated sources. noiseLevel : `float`, optional Standard deviation of the noise to add to each pixel. sourceSigma : `float`, optional Average amplitude of the simulated sources, relative to ``noiseLevel`` minPsfSize : `float`, optional The smallest PSF width (sigma) to use, in pixels. maxPsfSize : `float`, optional The largest PSF width (sigma) to use, in pixels. pixelScale : `lsst.geom.Angle`, optional The plate scale of the simulated images. ra : `lsst.geom.Angle`, optional Right Ascension of the boresight of the camera for the observation. dec : `lsst.geom.Angle`, optional Declination of the boresight of the camera for the observation. ccd : `int`, optional CCD number to put in the metadata of the exposure. patch : `int`, optional Unique identifier for a subdivision of a tract. patchGen2 : `str`, optional Unique identifier for a subdivision of a tract. In Gen 2 the patch identifier consists of two integers separated by a comma. tract : `int`, optional Unique identifier for a tract of a skyMap. Raises ------ ValueError If the bounding box does not contain the pixel coordinate (0, 0). This is due to `GaussianPsf` that is used by `lsst.meas.algorithms.testUtils.plantSources` lacking the option to specify the pixel origin. """ rotAngle = 0. * degrees "Rotation of the pixel grid on the sky, East from North (`lsst.geom.Angle`)." filterLabel = None """The filter definition, usually set in the current instruments' obs package. For these tests, a simple filter is defined without using an obs package (`lsst.afw.image.FilterLabel`). """ rngData = None """Pre-initialized random number generator for constructing the test images repeatably (`numpy.random.Generator`). """ rngMods = None """Pre-initialized random number generator for applying modifications to the test images for only some test cases (`numpy.random.Generator`). """ kernelSize = None "Width of the kernel used for simulating sources, in pixels." exposures = {} "The simulated test data, with variable PSF sizes (`dict` of `lsst.afw.image.Exposure`)" matchedExposures = {} """The simulated exposures, all with PSF width set to `maxPsfSize` (`dict` of `lsst.afw.image.Exposure`). """ photoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(27, 10) "The photometric zero point to use for converting counts to flux units (`lsst.afw.image.PhotoCalib`)." badMaskPlanes = ["NO_DATA", "BAD"] "Mask planes that, if set, the associated pixel should not be included in the coaddTempExp." detector = None "Properties of the CCD for the exposure (`lsst.afw.cameraGeom.Detector`)." def __init__(self, shape=geom.Extent2I(201, 301), offset=geom.Point2I(-123, -45), backgroundLevel=314.592, seed=42, nSrc=37, fluxRange=2., noiseLevel=5, sourceSigma=200., minPsfSize=1.5, maxPsfSize=3., pixelScale=0.2 * arcseconds, ra=209. * degrees, dec=-20.25 * degrees, ccd=37, patch=42, patchGen2="2,3", tract=0): self.ra = ra self.dec = dec self.pixelScale = pixelScale self.patch = patch self.patchGen2 = patchGen2 self.tract = tract self.filterLabel = afwImage.FilterLabel(band="gTest", physical="gTest") self.rngData = np.random.default_rng(seed) self.rngMods = np.random.default_rng(seed + 1) self.bbox = geom.Box2I(offset, shape) if not self.bbox.contains(0, 0): raise ValueError( f"The bounding box must contain the coordinate (0, 0). {repr(self.bbox)}" ) self.wcs = self.makeDummyWcs() # Set up properties of the simulations nSigmaForKernel = 5 self.kernelSize = (int(maxPsfSize * nSigmaForKernel + 0.5) // 2) * 2 + 1 # make sure it is odd bufferSize = self.kernelSize // 2 x0, y0 = self.bbox.getBegin() xSize, ySize = self.bbox.getDimensions() # Set the pixel coordinates and fluxes of the simulated sources. self.xLoc = self.rngData.random(nSrc) * ( xSize - 2 * bufferSize) + bufferSize + x0 self.yLoc = self.rngData.random(nSrc) * ( ySize - 2 * bufferSize) + bufferSize + y0 self.flux = (self.rngData.random(nSrc) * (fluxRange - 1.) + 1.) * sourceSigma * noiseLevel self.backgroundLevel = backgroundLevel self.noiseLevel = noiseLevel self.minPsfSize = minPsfSize self.maxPsfSize = maxPsfSize self.detector = DetectorWrapper(name=f"detector {ccd}", id=ccd).detector def setDummyCoaddInputs(self, exposure, expId): """Generate an `ExposureCatalog` as though the exposures had been processed using `warpAndPsfMatch`. Parameters ---------- exposure : `lsst.afw.image.Exposure` The exposure to construct a `CoaddInputs` `ExposureCatalog` for. expId : `int` A unique identifier for the visit. """ badPixelMask = afwImage.Mask.getPlaneBitMask(self.badMaskPlanes) nGoodPix = np.sum(exposure.getMask().getArray() & badPixelMask == 0) config = CoaddInputRecorderConfig() inputRecorder = CoaddInputRecorderTask(config=config, name="inputRecorder") tempExpInputRecorder = inputRecorder.makeCoaddTempExpRecorder(expId, num=1) tempExpInputRecorder.addCalExp(exposure, expId, nGoodPix) tempExpInputRecorder.finish(exposure, nGoodPix=nGoodPix) def makeCoaddTempExp(self, rawExposure, visitInfo, expId): """Add the metadata required by `AssembleCoaddTask` to an exposure. Parameters ---------- rawExposure : `lsst.afw.image.Exposure` The simulated exposure. visitInfo : `lsst.afw.image.VisitInfo` VisitInfo containing metadata for the exposure. expId : `int` A unique identifier for the visit. Returns ------- tempExp : `lsst.afw.image.Exposure` The exposure, with all of the metadata needed for coaddition. """ tempExp = rawExposure.clone() tempExp.setWcs(self.wcs) tempExp.setFilter(self.filterLabel) tempExp.setPhotoCalib(self.photoCalib) tempExp.getInfo().setVisitInfo(visitInfo) tempExp.getInfo().setDetector(self.detector) self.setDummyCoaddInputs(tempExp, expId) return tempExp def makeDummyWcs(self, rotAngle=None, pixelScale=None, crval=None, flipX=True): """Make a World Coordinate System object for testing. Parameters ---------- rotAngle : `lsst.geom.Angle` Rotation of the CD matrix, East from North pixelScale : `lsst.geom.Angle` Pixel scale of the projection. crval : `lsst.afw.geom.SpherePoint` Coordinates of the reference pixel of the wcs. flipX : `bool`, optional Flip the direction of increasing Right Ascension. Returns ------- wcs : `lsst.afw.geom.skyWcs.SkyWcs` A wcs that matches the inputs. """ if rotAngle is None: rotAngle = self.rotAngle if pixelScale is None: pixelScale = self.pixelScale if crval is None: crval = geom.SpherePoint(self.ra, self.dec) crpix = geom.Box2D(self.bbox).getCenter() cdMatrix = afwGeom.makeCdMatrix(scale=pixelScale, orientation=rotAngle, flipX=flipX) wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix) return wcs def makeDummyVisitInfo(self, exposureId, randomizeTime=False): """Make a self-consistent visitInfo object for testing. Parameters ---------- exposureId : `int`, optional Unique integer identifier for this observation. randomizeTime : `bool`, optional Add a random offset within a 6 hour window to the observation time. Returns ------- visitInfo : `lsst.afw.image.VisitInfo` VisitInfo for the exposure. """ lsstLat = -30.244639 * u.degree lsstLon = -70.749417 * u.degree lsstAlt = 2663. * u.m lsstTemperature = 20. * u.Celsius lsstHumidity = 40. # in percent lsstPressure = 73892. * u.pascal loc = EarthLocation(lat=lsstLat, lon=lsstLon, height=lsstAlt) time = Time(2000.0, format="jyear", scale="tt") if randomizeTime: # Pick a random time within a 6 hour window time += 6 * u.hour * (self.rngMods.random() - 0.5) radec = SkyCoord(dec=self.dec.asDegrees(), ra=self.ra.asDegrees(), unit='deg', obstime=time, frame='icrs', location=loc) airmass = float(1.0 / np.sin(radec.altaz.alt)) obsInfo = makeObservationInfo( location=loc, detector_exposure_id=exposureId, datetime_begin=time, datetime_end=time, boresight_airmass=airmass, boresight_rotation_angle=Angle(0. * u.degree), boresight_rotation_coord='sky', temperature=lsstTemperature, pressure=lsstPressure, relative_humidity=lsstHumidity, tracking_radec=radec, altaz_begin=radec.altaz, observation_type='science', ) visitInfo = MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo( obsInfo) return visitInfo def makeTestImage(self, expId, noiseLevel=None, psfSize=None, backgroundLevel=None, detectionSigma=5., badRegionBox=None): """Make a reproduceable PSF-convolved masked image for testing. Parameters ---------- expId : `int` A unique identifier to use to refer to the visit. noiseLevel : `float`, optional Standard deviation of the noise to add to each pixel. psfSize : `float`, optional Width of the PSF of the simulated sources, in pixels. backgroundLevel : `float`, optional Background value added to all pixels in the simulated images. detectionSigma : `float`, optional Threshold amplitude of the image to set the "DETECTED" mask. badRegionBox : `lsst.geom.Box2I`, optional Add a bad region bounding box (set to "BAD"). """ if backgroundLevel is None: backgroundLevel = self.backgroundLevel if noiseLevel is None: noiseLevel = 5. visitInfo = self.makeDummyVisitInfo(expId, randomizeTime=True) if psfSize is None: psfSize = self.rngMods.random() * ( self.maxPsfSize - self.minPsfSize) + self.minPsfSize nSrc = len(self.flux) sigmas = [psfSize for src in range(nSrc)] sigmasPsfMatched = [self.maxPsfSize for src in range(nSrc)] coordList = list(zip(self.xLoc, self.yLoc, self.flux, sigmas)) coordListPsfMatched = list( zip(self.xLoc, self.yLoc, self.flux, sigmasPsfMatched)) xSize, ySize = self.bbox.getDimensions() model = plantSources(self.bbox, self.kernelSize, self.backgroundLevel, coordList, addPoissonNoise=False) modelPsfMatched = plantSources(self.bbox, self.kernelSize, self.backgroundLevel, coordListPsfMatched, addPoissonNoise=False) model.variance.array = np.abs(model.image.array) + noiseLevel modelPsfMatched.variance.array = np.abs( modelPsfMatched.image.array) + noiseLevel noise = self.rngData.random((ySize, xSize)) * noiseLevel noise -= np.median(noise) model.image.array += noise modelPsfMatched.image.array += noise detectedMask = afwImage.Mask.getPlaneBitMask("DETECTED") detectionThreshold = self.backgroundLevel + detectionSigma * noiseLevel model.mask.array[ model.image.array > detectionThreshold] += detectedMask if badRegionBox is not None: model.mask[badRegionBox] = afwImage.Mask.getPlaneBitMask("BAD") exposure = self.makeCoaddTempExp(model, visitInfo, expId) matchedExposure = self.makeCoaddTempExp(modelPsfMatched, visitInfo, expId) return exposure, matchedExposure @staticmethod def makeGen2DataRefList(exposures, matchedExposures, tract=0, patch="2,3", coaddName="deep"): """Make data references from the simulated exposures that can be retrieved using the Gen 2 Butler API. Parameters ---------- tract : `int` Unique identifier for a tract of a skyMap. patch : `str` Unique identifier for a subdivision of a tract. coaddName : `str` The type of coadd being produced. Typically 'deep'. Returns ------- dataRefList : `list` of `MockGen2WarpReference` The data references. """ dataRefList = [] for expId in exposures: exposure = exposures[expId] exposurePsfMatched = matchedExposures[expId] dataRef = MockGen2WarpReference( exposure, exposurePsfMatched=exposurePsfMatched, coaddName=coaddName, tract=tract, patch=patch, visit=expId) dataRefList.append(dataRef) return dataRefList @staticmethod def makeDataRefList(exposures, matchedExposures, warpType, tract=0, patch=42, coaddName="deep"): """Make data references from the simulated exposures that can be retrieved using the Gen 3 Butler API. Parameters ---------- warpType : `str` Either 'direct' or 'psfMatched'. tract : `int`, optional Unique identifier for a tract of a skyMap. patch : `int`, optional Unique identifier for a subdivision of a tract. coaddName : `str`, optional The type of coadd being produced. Typically 'deep'. Returns ------- dataRefList : `list` of `MockWarpReference` The data references. Raises ------ ValueError If an unknown `warpType` is supplied. """ dataRefList = [] for expId in exposures: if warpType == 'direct': exposure = exposures[expId] elif warpType == 'psfMatched': exposure = matchedExposures[expId] else: raise ValueError( "warpType must be one of 'direct' or 'psfMatched'") dataRef = MockWarpReference(exposure, coaddName=coaddName, tract=tract, patch=patch, visit=expId) dataRefList.append(dataRef) return dataRefList
def makePhotoCalib(self, zeroPoint): fluxMag0 = 10**(0.4 * zeroPoint) return afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 1.0)
def __init__(self, **kwargs): pipeBase.Task.__init__(self, **kwargs) self.multi_matches = None self.calib = afwImage.makePhotoCalibFromCalibZeroPoint( 10**(0.4 * self.config.commonZp)) self.calibDict = defaultdict(dict)
def run(self, exposure, sourceCat, expId=0): """!Do photometric calibration - select matches to use and (possibly iteratively) compute the zero point. @param[in] exposure Exposure upon which the sources in the matches were detected. @param[in] sourceCat A catalog of sources to use in the calibration (@em i.e. a list of lsst.afw.table.Match with @c first being of type lsst.afw.table.SimpleRecord and @c second type lsst.afw.table.SourceRecord --- the reference object and matched object respectively). (will not be modified except to set the outputField if requested.). @return Struct of: - photoCalib -- @link lsst::afw::image::PhotoCalib@endlink object containing the calibration - arrays ------ Magnitude arrays returned be PhotoCalTask.extractMagArrays - matches ----- Final ReferenceMatchVector, as returned by PhotoCalTask.selectMatches. - zp ---------- Photometric zero point (mag) - sigma ------- Standard deviation of fit of photometric zero point (mag) - ngood ------- Number of sources used to fit photometric zero point The exposure is only used to provide the name of the filter being calibrated (it may also be used to generate debugging plots). The reference objects: - Must include a field @c photometric; True for objects which should be considered as photometric standards - Must include a field @c flux; the flux used to impose a magnitude limit and also to calibrate the data to (unless a color term is specified, in which case ColorTerm.primary is used; See https://jira.lsstcorp.org/browse/DM-933) - May include a field @c stargal; if present, True means that the object is a star - May include a field @c var; if present, True means that the object is variable The measured sources: - Must include PhotoCalConfig.fluxField; the flux measurement to be used for calibration @throws RuntimeError with the following strings: <DL> <DT> No matches to use for photocal <DD> No matches are available (perhaps no sources/references were selected by the matcher). <DT> No reference stars are available <DD> No matches are available from which to extract magnitudes. </DL> """ import lsstDebug display = lsstDebug.Info(__name__).display displaySources = display and lsstDebug.Info(__name__).displaySources self.scatterPlot = display and lsstDebug.Info(__name__).scatterPlot if self.scatterPlot: from matplotlib import pyplot try: self.fig.clf() except Exception: self.fig = pyplot.figure() filterName = exposure.getFilter().getName() # Match sources matchResults = self.match.run(sourceCat, filterName) matches = matchResults.matches reserveResults = self.reserve.run([mm.second for mm in matches], expId=expId) if displaySources: self.displaySources(exposure, matches, reserveResults.reserved) if reserveResults.reserved.sum() > 0: matches = [mm for mm, use in zip(matches, reserveResults.use) if use] if len(matches) == 0: raise RuntimeError("No matches to use for photocal") if self.usedKey is not None: for mm in matches: mm.second.set(self.usedKey, True) # Prepare for fitting sourceKeys = self.getSourceKeys(matches[0].second.schema) arrays = self.extractMagArrays(matches=matches, filterName=filterName, sourceKeys=sourceKeys) # Fit for zeropoint r = self.getZeroPoint(arrays.srcMag, arrays.refMag, arrays.magErr) self.log.info("Magnitude zero point: %f +/- %f from %d stars", r.zp, r.sigma, r.ngood) # Prepare the results flux0 = 10**(0.4*r.zp) # Flux of mag=0 star flux0err = 0.4*math.log(10)*flux0*r.sigma # Error in flux0 photoCalib = makePhotoCalibFromCalibZeroPoint(flux0, flux0err) return pipeBase.Struct( photoCalib=photoCalib, arrays=arrays, matches=matches, zp=r.zp, sigma=r.sigma, ngood=r.ngood, )
def setUp(self): tract = 0 band = 'r' patch = 0 visits = [100, 101] # Good to test crossing 0. ra_center = 0.0 dec_center = -45.0 pixel_scale = 0.2 coadd_zp = 27.0 # Generate a mock skymap with one patch config = DiscreteSkyMap.ConfigClass() config.raList = [ra_center] config.decList = [dec_center] config.radiusList = [150 * pixel_scale / 3600.] config.patchInnerDimensions = (350, 350) config.patchBorder = 50 config.tractOverlap = 0.0 config.pixelScale = pixel_scale sky_map = DiscreteSkyMap(config) visit_summaries = [ makeMockVisitSummary(visit, ra_center=ra_center, dec_center=dec_center) for visit in visits ] visit_summary_refs = [ MockVisitSummaryReference(visit_summary, visit) for visit_summary, visit in zip(visit_summaries, visits) ] self.visit_summary_dict = { visit: ref.get() for ref, visit in zip(visit_summary_refs, visits) } # Generate an input map. Note that this does not need to be consistent # with the visit_summary projections, we're just tracking values. input_map = hsp.HealSparseMap.make_empty( nside_coverage=256, nside_sparse=32768, dtype=hsp.WIDE_MASK, wide_mask_maxbits=len(visits) * 2) patch_poly = afwGeom.Polygon( geom.Box2D(sky_map[tract][patch].getOuterBBox())) sph_pts = sky_map[tract].getWcs().pixelToSky( patch_poly.convexHull().getVertices()) patch_poly_radec = np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees()) for sph in sph_pts]) poly = hsp.Polygon(ra=patch_poly_radec[:-1, 0], dec=patch_poly_radec[:-1, 1], value=[0]) poly_pixels = poly.get_pixels(nside=input_map.nside_sparse) # The input map has full coverage for bits 0 and 1 input_map.set_bits_pix(poly_pixels, [0]) input_map.set_bits_pix(poly_pixels, [1]) input_map_ref = MockInputMapReference(input_map, patch=patch, tract=tract) self.input_map_dict = {patch: input_map_ref} coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(), sky_map[tract].getWcs()) instFluxMag0 = 10.**(coadd_zp / 2.5) pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0) coadd.setPhotoCalib(pc) # Mock the coadd input ccd table schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("ccd", type="I") schema.addField("visit", type="I") schema.addField("weight", type="F") ccds = afwTable.ExposureCatalog(schema) ccds.resize(2) ccds['id'] = np.arange(2) ccds['visit'][0] = visits[0] ccds['visit'][1] = visits[1] ccds['ccd'][0] = 0 ccds['ccd'][1] = 1 ccds['weight'] = 10.0 for ccd_row in ccds: summary = self.visit_summary_dict[ccd_row['visit']].find( ccd_row['ccd']) ccd_row.setWcs(summary.getWcs()) ccd_row.setPsf(summary.getPsf()) ccd_row.setBBox(summary.getBBox()) ccd_row.setPhotoCalib(summary.getPhotoCalib()) inputs = afwImage.CoaddInputs() inputs.ccds = ccds coadd.getInfo().setCoaddInputs(inputs) coadd_ref = MockCoaddReference(coadd, patch=patch, tract=tract) self.coadd_dict = {patch: coadd_ref} self.tract = tract self.band = band self.sky_map = sky_map self.input_map = input_map
def run(self, exposure, sourceCat, expId=0): """!Do photometric calibration - select matches to use and (possibly iteratively) compute the zero point. @param[in] exposure Exposure upon which the sources in the matches were detected. @param[in] sourceCat A catalog of sources to use in the calibration (@em i.e. a list of lsst.afw.table.Match with @c first being of type lsst.afw.table.SimpleRecord and @c second type lsst.afw.table.SourceRecord --- the reference object and matched object respectively). (will not be modified except to set the outputField if requested.). @return Struct of: - photoCalib -- @link lsst::afw::image::PhotoCalib@endlink object containing the calibration - arrays ------ Magnitude arrays returned be PhotoCalTask.extractMagArrays - matches ----- Final ReferenceMatchVector, as returned by PhotoCalTask.selectMatches. - zp ---------- Photometric zero point (mag) - sigma ------- Standard deviation of fit of photometric zero point (mag) - ngood ------- Number of sources used to fit photometric zero point The exposure is only used to provide the name of the filter being calibrated (it may also be used to generate debugging plots). The reference objects: - Must include a field @c photometric; True for objects which should be considered as photometric standards - Must include a field @c flux; the flux used to impose a magnitude limit and also to calibrate the data to (unless a color term is specified, in which case ColorTerm.primary is used; See https://jira.lsstcorp.org/browse/DM-933) - May include a field @c stargal; if present, True means that the object is a star - May include a field @c var; if present, True means that the object is variable The measured sources: - Must include PhotoCalConfig.fluxField; the flux measurement to be used for calibration @throws RuntimeError with the following strings: <DL> <DT> No matches to use for photocal <DD> No matches are available (perhaps no sources/references were selected by the matcher). <DT> No reference stars are available <DD> No matches are available from which to extract magnitudes. </DL> """ import lsstDebug display = lsstDebug.Info(__name__).display displaySources = display and lsstDebug.Info(__name__).displaySources self.scatterPlot = display and lsstDebug.Info(__name__).scatterPlot if self.scatterPlot: from matplotlib import pyplot try: self.fig.clf() except Exception: self.fig = pyplot.figure() filterLabel = exposure.getFilterLabel() # Match sources matchResults = self.match.run(sourceCat, filterLabel.bandLabel) matches = matchResults.matches reserveResults = self.reserve.run([mm.second for mm in matches], expId=expId) if displaySources: self.displaySources(exposure, matches, reserveResults.reserved) if reserveResults.reserved.sum() > 0: matches = [ mm for mm, use in zip(matches, reserveResults.use) if use ] if len(matches) == 0: raise RuntimeError("No matches to use for photocal") if self.usedKey is not None: for mm in matches: mm.second.set(self.usedKey, True) # Prepare for fitting sourceKeys = self.getSourceKeys(matches[0].second.schema) arrays = self.extractMagArrays(matches, filterLabel, sourceKeys) # Fit for zeropoint r = self.getZeroPoint(arrays.srcMag, arrays.refMag, arrays.magErr) self.log.info("Magnitude zero point: %f +/- %f from %d stars", r.zp, r.sigma, r.ngood) # Prepare the results flux0 = 10**(0.4 * r.zp) # Flux of mag=0 star flux0err = 0.4 * math.log(10) * flux0 * r.sigma # Error in flux0 photoCalib = makePhotoCalibFromCalibZeroPoint(flux0, flux0err) return pipeBase.Struct( photoCalib=photoCalib, arrays=arrays, matches=matches, zp=r.zp, sigma=r.sigma, ngood=r.ngood, )
def makeMockVisitSummary(visit, ra_center=0.0, dec_center=-45.0, physical_filter='TEST-I', band='i', mjd=59234.7083333334, psf_sigma=3.0, zenith_distance=45.0, zero_point=30.0, sky_background=100.0, sky_noise=10.0, mean_var=100.0, exposure_time=100.0, detector_size=200, pixel_scale=0.2): """Make a mock visit summary catalog. This will contain two square detectors with the same metadata, with a small (20 pixel) gap between the detectors. There is no rotation, as each detector is simply offset in RA from the specified boresight. Parameters ---------- visit : `int` Visit number. ra_center : `float` Right ascension of the center of the "camera" boresight (degrees). dec_center : `float` Declination of the center of the "camera" boresight (degrees). physical_filter : `str` Arbitrary name for the physical filter. band : `str` Name of the associated band. mjd : `float` Modified Julian Date. psf_sigma : `float` Sigma width of Gaussian psf. zenith_distance : `float` Distance from zenith of the visit (degrees). zero_point : `float` Constant zero point for the visit (magnitudes). sky_background : `float` Background level for the visit (counts). sky_noise : `float` Noise level for the background of the visit (counts). mean_var : `float` Mean of the variance plane of the visit (counts). exposure_time : `float` Exposure time of the visit (seconds). detector_size : `int` Size of each square detector in the visit (pixels). pixel_scale : `float` Size of the pixel in arcseconds per pixel. Returns ------- visit_summary : `lsst.afw.table.ExposureCatalog` """ # We are making a 2 detector "camera" n_detector = 2 schema = ConsolidateVisitSummaryTask()._makeVisitSummarySchema() visit_summary = afwTable.ExposureCatalog(schema) visit_summary.resize(n_detector) bbox = geom.Box2I(x=geom.IntervalI(min=0, max=detector_size - 1), y=geom.IntervalI(min=0, max=detector_size - 1)) for detector_id in range(n_detector): row = visit_summary[detector_id] row['id'] = detector_id row.setBBox(bbox) row['visit'] = visit row['physical_filter'] = physical_filter row['band'] = band row['zenithDistance'] = zenith_distance row['zeroPoint'] = zero_point row['skyBg'] = sky_background row['skyNoise'] = sky_noise row['meanVar'] = mean_var # Generate a photocalib instFluxMag0 = 10.**(zero_point / 2.5) row.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0)) # Generate a WCS and set values accordingly crpix = geom.Point2D(detector_size / 2., detector_size / 2.) # Create a 20 pixel gap between the two detectors (each offset 10 pixels). if detector_id == 0: delta_ra = -1.0 * ((detector_size + 10) * pixel_scale / 3600.) / np.cos(np.deg2rad(dec_center)) delta_dec = 0.0 elif detector_id == 1: delta_ra = ((detector_size + 10) * pixel_scale / 3600.) / np.cos( np.deg2rad(dec_center)) delta_dec = 0.0 crval = geom.SpherePoint(ra_center + delta_ra, dec_center + delta_dec, geom.degrees) cd_matrix = afwGeom.makeCdMatrix(scale=pixel_scale * geom.arcseconds, orientation=0.0 * geom.degrees) wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd_matrix) row.setWcs(wcs) sph_pts = wcs.pixelToSky(geom.Box2D(bbox).getCorners()) row['raCorners'] = np.array( [float(sph.getRa().asDegrees()) for sph in sph_pts]) row['decCorners'] = np.array( [float(sph.getDec().asDegrees()) for sph in sph_pts]) sph_pt = wcs.pixelToSky(bbox.getCenter()) row['ra'] = sph_pt.getRa().asDegrees() row['decl'] = sph_pt.getDec().asDegrees() # Generate a visitInfo. # This does not need to be consistent with the zenith angle in the table, # it just needs to be valid and have sufficient information to compute # exposure time and parallactic angle. date = DateTime(date=mjd, system=DateTime.DateSystem.MJD) visit_info = afwImage.VisitInfo( exposureId=visit, exposureTime=exposure_time, date=date, darkTime=0.0, boresightRaDec=geom.SpherePoint(ra_center, dec_center, geom.degrees), era=45.1 * geom.degrees, observatory=Observatory(11.1 * geom.degrees, 0.0 * geom.degrees, 0.333), boresightRotAngle=0.0 * geom.degrees, rotType=afwImage.RotType.SKY) row.setVisitInfo(visit_info) # Generate a PSF and set values accordingly psf = GaussianPsf(15, 15, psf_sigma) row.setPsf(psf) psfAvgPos = psf.getAveragePosition() shape = psf.computeShape(psfAvgPos) row['psfSigma'] = psf.getSigma() row['psfIxx'] = shape.getIxx() row['psfIyy'] = shape.getIyy() row['psfIxy'] = shape.getIxy() row['psfArea'] = shape.getArea() return visit_summary