Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of fgcmcal. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21"""Base class for running fgcmcal on a single tract using src tables 

22or sourceTable_visit tables. 

23""" 

24 

25import sys 

26import traceback 

27import abc 

28 

29import numpy as np 

30 

31import lsst.daf.persistence as dafPersist 

32import lsst.pex.config as pexConfig 

33import lsst.pipe.base as pipeBase 

34 

35from .fgcmBuildStars import FgcmBuildStarsTask, FgcmBuildStarsConfig 

36from .fgcmFitCycle import FgcmFitCycleConfig 

37from .fgcmOutputProducts import FgcmOutputProductsTask 

38from .utilities import makeConfigDict, translateFgcmLut, translateVisitCatalog 

39from .utilities import computeCcdOffsets, computeApertureRadiusFromDataRef, extractReferenceMags 

40from .utilities import makeZptSchema, makeZptCat 

41from .utilities import makeAtmSchema, makeAtmCat 

42from .utilities import makeStdSchema, makeStdCat 

43 

44import fgcm 

45 

46__all__ = ['FgcmCalibrateTractConfigBase', 'FgcmCalibrateTractBaseTask', 'FgcmCalibrateTractRunner'] 

47 

48 

49class FgcmCalibrateTractConfigBase(pexConfig.Config): 

50 """Config for FgcmCalibrateTract""" 

51 

52 fgcmBuildStars = pexConfig.ConfigurableField( 

53 target=FgcmBuildStarsTask, 

54 doc="Task to load and match stars for fgcm", 

55 ) 

56 fgcmFitCycle = pexConfig.ConfigField( 

57 dtype=FgcmFitCycleConfig, 

58 doc="Config to run a single fgcm fit cycle", 

59 ) 

60 fgcmOutputProducts = pexConfig.ConfigurableField( 

61 target=FgcmOutputProductsTask, 

62 doc="Task to output fgcm products", 

63 ) 

64 convergenceTolerance = pexConfig.Field( 

65 doc="Tolerance on repeatability convergence (per band)", 

66 dtype=float, 

67 default=0.005, 

68 ) 

69 maxFitCycles = pexConfig.Field( 

70 doc="Maximum number of fit cycles", 

71 dtype=int, 

72 default=5, 

73 ) 

74 doDebuggingPlots = pexConfig.Field( 

75 doc="Make plots for debugging purposes?", 

76 dtype=bool, 

77 default=False, 

78 ) 

79 

80 def setDefaults(self): 

81 pexConfig.Config.setDefaults(self) 

82 

83 self.fgcmFitCycle.quietMode = True 

84 self.fgcmFitCycle.doPlots = False 

85 self.fgcmOutputProducts.doReferenceCalibration = False 

86 self.fgcmOutputProducts.doRefcatOutput = False 

87 self.fgcmOutputProducts.cycleNumber = 0 

88 self.fgcmOutputProducts.photoCal.applyColorTerms = False 

89 

90 def validate(self): 

91 super().validate() 

92 

93 for band in self.fgcmFitCycle.bands: 

94 if not self.fgcmFitCycle.useRepeatabilityForExpGrayCutsDict[band]: 

95 msg = 'Must set useRepeatabilityForExpGrayCutsDict[band]=True for all bands' 

96 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.useRepeatabilityForExpGrayCutsDict, 

97 self, msg) 

98 

99 

100class FgcmCalibrateTractRunner(pipeBase.ButlerInitializedTaskRunner): 

101 """Subclass of TaskRunner for FgcmCalibrateTractTask 

102 

103 fgcmCalibrateTractTask.run() takes a number of arguments, one of which is 

104 the butler (for persistence and mapper data), and a list of dataRefs 

105 extracted from the command line. This task runs on a constrained set 

106 of dataRefs, typically a single tract. 

107 This class transforms the process arguments generated by the ArgumentParser 

108 into the arguments expected by FgcmCalibrateTractTask.run(). 

109 This runner does not use any parallelization. 

110 """ 

111 

112 @staticmethod 

113 def getTargetList(parsedCmd): 

114 """ 

115 Return a list with one element: a tuple with the butler and 

116 list of dataRefs. 

117 """ 

118 return [(parsedCmd.butler, parsedCmd.id.refList)] 

119 

120 def __call__(self, args): 

121 """ 

122 Parameters 

123 ---------- 

124 args: `tuple` with (butler, dataRefList) 

125 

126 Returns 

127 ------- 

128 exitStatus: `list` with `lsst.pipe.base.Struct` 

129 exitStatus (0: success; 1: failure) 

130 May also contain results if `self.doReturnResults` is `True`. 

131 """ 

132 butler, dataRefList = args 

133 

134 task = self.TaskClass(config=self.config, log=self.log) 

135 

136 exitStatus = 0 

137 if self.doRaise: 

138 results = task.runDataRef(butler, dataRefList) 

139 else: 

140 try: 

141 results = task.runDataRef(butler, dataRefList) 

142 except Exception as e: 

143 exitStatus = 1 

144 task.log.fatal("Failed: %s" % e) 

145 if not isinstance(e, pipeBase.TaskError): 

146 traceback.print_exc(file=sys.stderr) 

147 

148 task.writeMetadata(butler) 

149 

150 if self.doReturnResults: 

151 return [pipeBase.Struct(exitStatus=exitStatus, 

152 results=results)] 

153 else: 

154 return [pipeBase.Struct(exitStatus=exitStatus)] 

155 

156 def run(self, parsedCmd): 

157 """ 

158 Run the task, with no multiprocessing 

159 

160 Parameters 

161 ---------- 

162 parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line 

163 """ 

164 

165 resultList = [] 

166 

167 if self.precall(parsedCmd): 

168 targetList = self.getTargetList(parsedCmd) 

169 resultList = self(targetList[0]) 

170 

171 return resultList 

172 

173 

174class FgcmCalibrateTractBaseTask(pipeBase.PipelineTask, pipeBase.CmdLineTask, abc.ABC): 

175 """Base class to calibrate a single tract using fgcmcal 

176 """ 

177 def __init__(self, butler=None, **kwargs): 

178 """ 

179 Instantiate an `FgcmCalibrateTractTask`. 

180 

181 Parameters 

182 ---------- 

183 butler : `lsst.daf.persistence.Butler`, optional 

184 """ 

185 super().__init__(**kwargs) 

186 self.makeSubtask("fgcmBuildStars", butler=butler) 

187 self.makeSubtask("fgcmOutputProducts", butler=butler) 

188 

189 # no saving of metadata for now 

190 def _getMetadataName(self): 

191 return None 

192 

193 @pipeBase.timeMethod 

194 def runDataRef(self, butler, dataRefs): 

195 """ 

196 Run full FGCM calibration on a single tract, including building star list, 

197 fitting multiple cycles, and making outputs. 

198 

199 Parameters 

200 ---------- 

201 butler: `lsst.daf.persistence.Butler` 

202 dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef` 

203 Data references for the input visits. 

204 These may be either per-ccd "src" or per-visit"sourceTable_visit" 

205 references. 

206 

207 Raises 

208 ------ 

209 RuntimeError: Raised if `config.fgcmBuildStars.doReferenceMatches` is 

210 not True, or if fgcmLookUpTable is not available, or if 

211 doSubtractLocalBackground is True and aperture radius cannot be 

212 determined. 

213 """ 

214 datasetType = dataRefs[0].butlerSubset.datasetType 

215 self.log.info("Running with %d %s dataRefs" % (len(dataRefs), datasetType)) 

216 

217 if not butler.datasetExists('fgcmLookUpTable'): 

218 raise RuntimeError("Must run FgcmCalibrateTract with an fgcmLookUpTable") 

219 

220 if not self.config.fgcmBuildStars.doReferenceMatches: 

221 raise RuntimeError("Must run FgcmCalibrateTract with fgcmBuildStars.doReferenceMatches") 

222 if isinstance(self.config.fgcmBuildStars, FgcmBuildStarsConfig): 

223 if self.config.fgcmBuildStars.checkAllCcds: 

224 raise RuntimeError("Cannot run FgcmCalibrateTract with " 

225 "fgcmBuildStars.checkAllCcds set to True") 

226 

227 tract = int(dataRefs[0].dataId['tract']) 

228 camera = butler.get('camera') 

229 

230 dataRefDict = {} 

231 dataRefDict['camera'] = camera 

232 dataRefDict['source_catalogs'] = dataRefs 

233 dataRefDict['sourceSchema'] = butler.dataRef('src_schema') 

234 dataRefDict['fgcmLookUpTable'] = butler.dataRef('fgcmLookUpTable') 

235 

236 struct = self.run(dataRefDict, tract, butler=butler, returnCatalogs=False) 

237 

238 visitDataRefName = self.config.fgcmBuildStars.visitDataRefName 

239 ccdDataRefName = self.config.fgcmBuildStars.ccdDataRefName 

240 

241 if struct.photoCalibs is not None: 

242 self.log.info("Outputting photoCalib files.") 

243 

244 for visit, detector, physicalFilter, photoCalib in struct.photoCalibs: 

245 butler.put(photoCalib, 'fgcm_tract_photoCalib', 

246 dataId={visitDataRefName: visit, 

247 ccdDataRefName: detector, 

248 'filter': physicalFilter, 

249 'tract': tract}) 

250 

251 self.log.info("Done outputting photoCalib files.") 

252 

253 if struct.atmospheres is not None: 

254 self.log.info("Outputting atmosphere files.") 

255 for visit, atm in struct.atmospheres: 

256 butler.put(atm, "transmission_atmosphere_fgcm_tract", 

257 dataId={visitDataRefName: visit, 

258 'tract': tract}) 

259 self.log.info("Done outputting atmosphere transmissions.") 

260 

261 return pipeBase.Struct(repeatability=struct.repeatability) 

262 

263 def run(self, dataRefDict, tract, 

264 buildStarsRefObjLoader=None, returnCatalogs=True, butler=None): 

265 """Run the calibrations for a single tract with fgcm. 

266 

267 Parameters 

268 ---------- 

269 dataRefDict : `dict` 

270 All dataRefs are `lsst.daf.persistence.ButlerDataRef` (gen2) or 

271 `lsst.daf.butler.DeferredDatasetHandle` (gen3) 

272 dataRef dictionary with the following keys. Note that all 

273 keys need not be set based on config parameters. 

274 

275 ``"camera"`` 

276 Camera object (`lsst.afw.cameraGeom.Camera`) 

277 ``"source_catalogs"`` 

278 `list` of dataRefs for input source catalogs. 

279 ``"sourceSchema"`` 

280 Schema for the source catalogs. 

281 ``"fgcmLookUpTable"`` 

282 dataRef for the FGCM look-up table. 

283 ``"calexps"`` 

284 `list` of dataRefs for the input calexps (Gen3 only) 

285 ``"fgcmPhotoCalibs"`` 

286 `dict` of output photoCalib dataRefs. Key is 

287 (tract, visit, detector). (Gen3 only) 

288 Present if doZeropointOutput is True. 

289 ``"fgcmTransmissionAtmospheres"`` 

290 `dict` of output atmosphere transmission dataRefs. 

291 Key is (tract, visit). (Gen3 only) 

292 Present if doAtmosphereOutput is True. 

293 tract : `int` 

294 Tract number 

295 buildStarsRefObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`, optional 

296 Reference object loader object for fgcmBuildStars. 

297 returnCatalogs : `bool`, optional 

298 Return photoCalibs as per-visit exposure catalogs. 

299 butler : `lsst.daf.persistence.Butler`, optional 

300 Gen2 butler used for reference star outputs 

301 

302 Returns 

303 ------- 

304 outstruct : `lsst.pipe.base.Struct` 

305 Output structure with keys: 

306 

307 offsets : `np.ndarray` 

308 Final reference offsets, per band. 

309 repeatability : `np.ndarray` 

310 Raw fgcm repeatability for bright stars, per band. 

311 atmospheres : `generator` [(`int`, `lsst.afw.image.TransmissionCurve`)] 

312 Generator that returns (visit, transmissionCurve) tuples. 

313 photoCalibs : `generator` [(`int`, `int`, `str`, `lsst.afw.image.PhotoCalib`)] 

314 Generator that returns (visit, ccd, filtername, photoCalib) tuples. 

315 (returned if returnCatalogs is False). 

316 photoCalibCatalogs : `generator` [(`int`, `lsst.afw.table.ExposureCatalog`)] 

317 Generator that returns (visit, exposureCatalog) tuples. 

318 (returned if returnCatalogs is True). 

319 """ 

320 self.log.info("Running on tract %d", (tract)) 

321 

322 # Compute the aperture radius if necessary. This is useful to do now before 

323 # any heavy lifting has happened (fail early). 

324 calibFluxApertureRadius = None 

325 if self.config.fgcmBuildStars.doSubtractLocalBackground: 

326 try: 

327 field = self.config.fgcmBuildStars.instFluxField 

328 calibFluxApertureRadius = computeApertureRadiusFromDataRef(dataRefDict['source_catalogs'][0], 

329 field) 

330 except RuntimeError: 

331 raise RuntimeError("Could not determine aperture radius from %s. " 

332 "Cannot use doSubtractLocalBackground." % 

333 (field)) 

334 

335 # Run the build stars tasks 

336 

337 # Note that we will need visitCat at the end of the procedure for the outputs 

338 if isinstance(butler, dafPersist.Butler): 

339 # Gen2 

340 groupedDataRefs = self.fgcmBuildStars._findAndGroupDataRefsGen2(butler, dataRefDict['camera'], 

341 dataRefDict['source_catalogs']) 

342 else: 

343 # Gen3 

344 groupedDataRefs = self.fgcmBuildStars._groupDataRefs(dataRefDict['sourceTableDataRefDict'], 

345 dataRefDict['visitSummaryDataRefDict']) 

346 visitCat = self.fgcmBuildStars.fgcmMakeVisitCatalog(dataRefDict['camera'], groupedDataRefs) 

347 rad = calibFluxApertureRadius 

348 fgcmStarObservationCat = self.fgcmBuildStars.fgcmMakeAllStarObservations(groupedDataRefs, 

349 visitCat, 

350 dataRefDict['sourceSchema'], 

351 dataRefDict['camera'], 

352 calibFluxApertureRadius=rad) 

353 

354 if self.fgcmBuildStars.config.doReferenceMatches: 

355 lutDataRef = dataRefDict['fgcmLookUpTable'] 

356 if buildStarsRefObjLoader is not None: 

357 self.fgcmBuildStars.makeSubtask("fgcmLoadReferenceCatalog", 

358 refObjLoader=buildStarsRefObjLoader) 

359 else: 

360 self.fgcmBuildStars.makeSubtask("fgcmLoadReferenceCatalog", butler=butler) 

361 else: 

362 lutDataRef = None 

363 

364 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = \ 

365 self.fgcmBuildStars.fgcmMatchStars(visitCat, 

366 fgcmStarObservationCat, 

367 lutDataRef=lutDataRef) 

368 

369 # Load the LUT 

370 lutCat = dataRefDict['fgcmLookUpTable'].get() 

371 fgcmLut, lutIndexVals, lutStd = translateFgcmLut(lutCat, 

372 dict(self.config.fgcmFitCycle.physicalFilterMap)) 

373 del lutCat 

374 

375 # Translate the visit catalog into fgcm format 

376 fgcmExpInfo = translateVisitCatalog(visitCat) 

377 

378 configDict = makeConfigDict(self.config.fgcmFitCycle, self.log, dataRefDict['camera'], 

379 self.config.fgcmFitCycle.maxIterBeforeFinalCycle, 

380 True, False, lutIndexVals[0]['FILTERNAMES'], 

381 tract=tract) 

382 

383 # Use the first orientation. 

384 # TODO: DM-21215 will generalize to arbitrary camera orientations 

385 ccdOffsets = computeCcdOffsets(dataRefDict['camera'], fgcmExpInfo['TELROT'][0]) 

386 

387 # Set up the fit cycle task 

388 

389 noFitsDict = {'lutIndex': lutIndexVals, 

390 'lutStd': lutStd, 

391 'expInfo': fgcmExpInfo, 

392 'ccdOffsets': ccdOffsets} 

393 

394 fgcmFitCycle = fgcm.FgcmFitCycle(configDict, useFits=False, 

395 noFitsDict=noFitsDict, noOutput=True) 

396 

397 # We determine the conversion from the native units (typically radians) to 

398 # degrees for the first star. This allows us to treat coord_ra/coord_dec as 

399 # numpy arrays rather than Angles, which would we approximately 600x slower. 

400 conv = fgcmStarObservationCat[0]['ra'].asDegrees() / float(fgcmStarObservationCat[0]['ra']) 

401 

402 # To load the stars, we need an initial parameter object 

403 fgcmPars = fgcm.FgcmParameters.newParsWithArrays(fgcmFitCycle.fgcmConfig, 

404 fgcmLut, 

405 fgcmExpInfo) 

406 

407 # Match star observations to visits 

408 # Only those star observations that match visits from fgcmExpInfo['VISIT'] will 

409 # actually be transferred into fgcm using the indexing below. 

410 

411 obsIndex = fgcmStarIndicesCat['obsIndex'] 

412 visitIndex = np.searchsorted(fgcmExpInfo['VISIT'], 

413 fgcmStarObservationCat['visit'][obsIndex]) 

414 

415 refMag, refMagErr = extractReferenceMags(fgcmRefCat, 

416 self.config.fgcmFitCycle.bands, 

417 self.config.fgcmFitCycle.physicalFilterMap) 

418 refId = fgcmRefCat['fgcm_id'][:] 

419 

420 fgcmStars = fgcm.FgcmStars(fgcmFitCycle.fgcmConfig) 

421 fgcmStars.loadStars(fgcmPars, 

422 fgcmStarObservationCat['visit'][obsIndex], 

423 fgcmStarObservationCat['ccd'][obsIndex], 

424 fgcmStarObservationCat['ra'][obsIndex] * conv, 

425 fgcmStarObservationCat['dec'][obsIndex] * conv, 

426 fgcmStarObservationCat['instMag'][obsIndex], 

427 fgcmStarObservationCat['instMagErr'][obsIndex], 

428 fgcmExpInfo['FILTERNAME'][visitIndex], 

429 fgcmStarIdCat['fgcm_id'][:], 

430 fgcmStarIdCat['ra'][:], 

431 fgcmStarIdCat['dec'][:], 

432 fgcmStarIdCat['obsArrIndex'][:], 

433 fgcmStarIdCat['nObs'][:], 

434 obsX=fgcmStarObservationCat['x'][obsIndex], 

435 obsY=fgcmStarObservationCat['y'][obsIndex], 

436 obsDeltaMagBkg=fgcmStarObservationCat['deltaMagBkg'][obsIndex], 

437 psfCandidate=fgcmStarObservationCat['psf_candidate'][obsIndex], 

438 refID=refId, 

439 refMag=refMag, 

440 refMagErr=refMagErr, 

441 flagID=None, 

442 flagFlag=None, 

443 computeNobs=True) 

444 

445 # Clear out some memory 

446 del fgcmStarIdCat 

447 del fgcmStarIndicesCat 

448 del fgcmRefCat 

449 

450 fgcmFitCycle.setLUT(fgcmLut) 

451 fgcmFitCycle.setStars(fgcmStars, fgcmPars) 

452 

453 converged = False 

454 cycleNumber = 0 

455 

456 previousReservedRawRepeatability = np.zeros(fgcmPars.nBands) + 1000.0 

457 previousParInfo = None 

458 previousParams = None 

459 previousSuperStar = None 

460 

461 while (not converged and cycleNumber < self.config.maxFitCycles): 

462 

463 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber) 

464 

465 if cycleNumber > 0: 

466 # Use parameters from previous cycle 

467 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig, 

468 fgcmExpInfo, 

469 previousParInfo, 

470 previousParams, 

471 previousSuperStar) 

472 # We need to reset the star magnitudes and errors for the next 

473 # cycle 

474 fgcmFitCycle.fgcmStars.reloadStarMagnitudes(fgcmStarObservationCat['instMag'][obsIndex], 

475 fgcmStarObservationCat['instMagErr'][obsIndex]) 

476 fgcmFitCycle.initialCycle = False 

477 

478 fgcmFitCycle.setPars(fgcmPars) 

479 fgcmFitCycle.finishSetup() 

480 

481 fgcmFitCycle.run() 

482 

483 # Grab the parameters for the next cycle 

484 previousParInfo, previousParams = fgcmFitCycle.fgcmPars.parsToArrays() 

485 previousSuperStar = fgcmFitCycle.fgcmPars.parSuperStarFlat.copy() 

486 

487 self.log.info("Raw repeatability after cycle number %d is:" % (cycleNumber)) 

488 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands): 

489 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]: 

490 continue 

491 rep = fgcmFitCycle.fgcmPars.compReservedRawRepeatability[i] * 1000.0 

492 self.log.info(" Band %s, repeatability: %.2f mmag" % (band, rep)) 

493 

494 # Check for convergence 

495 if np.alltrue((previousReservedRawRepeatability 

496 - fgcmFitCycle.fgcmPars.compReservedRawRepeatability) 

497 < self.config.convergenceTolerance): 

498 self.log.info("Raw repeatability has converged after cycle number %d." % (cycleNumber)) 

499 converged = True 

500 else: 

501 fgcmFitCycle.fgcmConfig.expGrayPhotometricCut[:] = fgcmFitCycle.updatedPhotometricCut 

502 fgcmFitCycle.fgcmConfig.expGrayHighCut[:] = fgcmFitCycle.updatedHighCut 

503 fgcmFitCycle.fgcmConfig.precomputeSuperStarInitialCycle = False 

504 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False 

505 previousReservedRawRepeatability[:] = fgcmFitCycle.fgcmPars.compReservedRawRepeatability 

506 self.log.info("Setting exposure gray photometricity cuts to:") 

507 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands): 

508 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]: 

509 continue 

510 cut = fgcmFitCycle.updatedPhotometricCut[i] * 1000.0 

511 self.log.info(" Band %s, photometricity cut: %.2f mmag" % (band, cut)) 

512 

513 cycleNumber += 1 

514 

515 # Log warning if not converged 

516 if not converged: 

517 self.log.warn("Maximum number of fit cycles exceeded (%d) without convergence." % (cycleNumber)) 

518 

519 # Do final clean-up iteration 

520 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False 

521 fgcmFitCycle.fgcmConfig.resetParameters = False 

522 fgcmFitCycle.fgcmConfig.maxIter = 0 

523 fgcmFitCycle.fgcmConfig.outputZeropoints = True 

524 fgcmFitCycle.fgcmConfig.outputStandards = True 

525 fgcmFitCycle.fgcmConfig.doPlots = self.config.doDebuggingPlots 

526 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber) 

527 fgcmFitCycle.initialCycle = False 

528 

529 fgcmPars = fgcm.FgcmParameters.loadParsWithArrays(fgcmFitCycle.fgcmConfig, 

530 fgcmExpInfo, 

531 previousParInfo, 

532 previousParams, 

533 previousSuperStar) 

534 fgcmFitCycle.fgcmStars.reloadStarMagnitudes(fgcmStarObservationCat['instMag'][obsIndex], 

535 fgcmStarObservationCat['instMagErr'][obsIndex]) 

536 fgcmFitCycle.setPars(fgcmPars) 

537 fgcmFitCycle.finishSetup() 

538 

539 self.log.info("Running final clean-up fit cycle...") 

540 fgcmFitCycle.run() 

541 

542 self.log.info("Raw repeatability after clean-up cycle is:") 

543 for i, band in enumerate(fgcmFitCycle.fgcmPars.bands): 

544 if not fgcmFitCycle.fgcmPars.hasExposuresInBand[i]: 

545 continue 

546 rep = fgcmFitCycle.fgcmPars.compReservedRawRepeatability[i] * 1000.0 

547 self.log.info(" Band %s, repeatability: %.2f mmag" % (band, rep)) 

548 

549 # Do the outputs. Need to keep track of tract. 

550 

551 superStarChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_SSTAR_CHEB'].shape[1] 

552 zptChebSize = fgcmFitCycle.fgcmZpts.zpStruct['FGCM_FZPT_CHEB'].shape[1] 

553 

554 zptSchema = makeZptSchema(superStarChebSize, zptChebSize) 

555 zptCat = makeZptCat(zptSchema, fgcmFitCycle.fgcmZpts.zpStruct) 

556 

557 atmSchema = makeAtmSchema() 

558 atmCat = makeAtmCat(atmSchema, fgcmFitCycle.fgcmZpts.atmStruct) 

559 

560 stdStruct, goodBands = fgcmFitCycle.fgcmStars.retrieveStdStarCatalog(fgcmFitCycle.fgcmPars) 

561 stdSchema = makeStdSchema(len(goodBands)) 

562 stdCat = makeStdCat(stdSchema, stdStruct, goodBands) 

563 

564 outStruct = self.fgcmOutputProducts.generateTractOutputProducts(dataRefDict, 

565 tract, 

566 visitCat, 

567 zptCat, atmCat, stdCat, 

568 self.config.fgcmBuildStars, 

569 returnCatalogs=returnCatalogs, 

570 butler=butler) 

571 

572 outStruct.repeatability = fgcmFitCycle.fgcmPars.compReservedRawRepeatability 

573 

574 return outStruct