Coverage for python/lsst/fgcmcal/fgcmCalibrateTractBase.py: 14%

Shortcuts 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

222 statements  

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 

34from lsst.utils.timer import timeMethod 

35 

36from .fgcmBuildStars import FgcmBuildStarsTask, FgcmBuildStarsConfig 

37from .fgcmFitCycle import FgcmFitCycleConfig 

38from .fgcmOutputProducts import FgcmOutputProductsTask 

39from .utilities import makeConfigDict, translateFgcmLut, translateVisitCatalog 

40from .utilities import computeCcdOffsets, computeApertureRadiusFromDataRef, extractReferenceMags 

41from .utilities import makeZptSchema, makeZptCat 

42from .utilities import makeAtmSchema, makeAtmCat 

43from .utilities import makeStdSchema, makeStdCat 

44 

45import fgcm 

46 

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

48 

49 

50class FgcmCalibrateTractConfigBase(pexConfig.Config): 

51 """Config for FgcmCalibrateTract""" 

52 

53 fgcmBuildStars = pexConfig.ConfigurableField( 

54 target=FgcmBuildStarsTask, 

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

56 ) 

57 fgcmFitCycle = pexConfig.ConfigField( 

58 dtype=FgcmFitCycleConfig, 

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

60 ) 

61 fgcmOutputProducts = pexConfig.ConfigurableField( 

62 target=FgcmOutputProductsTask, 

63 doc="Task to output fgcm products", 

64 ) 

65 convergenceTolerance = pexConfig.Field( 

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

67 dtype=float, 

68 default=0.005, 

69 ) 

70 maxFitCycles = pexConfig.Field( 

71 doc="Maximum number of fit cycles", 

72 dtype=int, 

73 default=5, 

74 ) 

75 doDebuggingPlots = pexConfig.Field( 

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

77 dtype=bool, 

78 default=False, 

79 ) 

80 

81 def setDefaults(self): 

82 pexConfig.Config.setDefaults(self) 

83 

84 self.fgcmFitCycle.quietMode = True 

85 self.fgcmFitCycle.doPlots = False 

86 self.fgcmOutputProducts.doReferenceCalibration = False 

87 self.fgcmOutputProducts.doRefcatOutput = False 

88 self.fgcmOutputProducts.cycleNumber = 0 

89 self.fgcmOutputProducts.photoCal.applyColorTerms = False 

90 

91 def validate(self): 

92 super().validate() 

93 

94 for band in self.fgcmFitCycle.bands: 

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

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

97 raise pexConfig.FieldValidationError(FgcmFitCycleConfig.useRepeatabilityForExpGrayCutsDict, 

98 self, msg) 

99 

100 

101class FgcmCalibrateTractRunner(pipeBase.ButlerInitializedTaskRunner): 

102 """Subclass of TaskRunner for FgcmCalibrateTractTask 

103 

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

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

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

107 of dataRefs, typically a single tract. 

108 This class transforms the process arguments generated by the ArgumentParser 

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

110 This runner does not use any parallelization. 

111 """ 

112 

113 @staticmethod 

114 def getTargetList(parsedCmd): 

115 """ 

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

117 list of dataRefs. 

118 """ 

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

120 

121 def __call__(self, args): 

122 """ 

123 Parameters 

124 ---------- 

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

126 

127 Returns 

128 ------- 

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

130 exitStatus (0: success; 1: failure) 

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

132 """ 

133 butler, dataRefList = args 

134 

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

136 

137 exitStatus = 0 

138 if self.doRaise: 

139 results = task.runDataRef(butler, dataRefList) 

140 else: 

141 try: 

142 results = task.runDataRef(butler, dataRefList) 

143 except Exception as e: 

144 exitStatus = 1 

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

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

147 traceback.print_exc(file=sys.stderr) 

148 

149 task.writeMetadata(butler) 

150 

151 if self.doReturnResults: 

152 return [pipeBase.Struct(exitStatus=exitStatus, 

153 results=results)] 

154 else: 

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

156 

157 def run(self, parsedCmd): 

158 """ 

159 Run the task, with no multiprocessing 

160 

161 Parameters 

162 ---------- 

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

164 """ 

165 

166 resultList = [] 

167 

168 if self.precall(parsedCmd): 

169 targetList = self.getTargetList(parsedCmd) 

170 resultList = self(targetList[0]) 

171 

172 return resultList 

173 

174 

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

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

177 """ 

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

179 """ 

180 Instantiate an `FgcmCalibrateTractTask`. 

181 

182 Parameters 

183 ---------- 

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

185 """ 

186 super().__init__(**kwargs) 

187 self.makeSubtask("fgcmBuildStars", initInputs=initInputs, butler=butler) 

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

189 

190 # no saving of metadata for now 

191 def _getMetadataName(self): 

192 return None 

193 

194 @timeMethod 

195 def runDataRef(self, butler, dataRefs): 

196 """ 

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

198 fitting multiple cycles, and making outputs. 

199 

200 Parameters 

201 ---------- 

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

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

204 Data references for the input visits. 

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

206 references. 

207 

208 Raises 

209 ------ 

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

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

212 doSubtractLocalBackground is True and aperture radius cannot be 

213 determined. 

214 """ 

215 datasetType = dataRefs[0].butlerSubset.datasetType 

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

217 

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

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

220 

221 if not self.config.fgcmBuildStars.doReferenceMatches: 

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

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

224 if self.config.fgcmBuildStars.checkAllCcds: 

225 raise RuntimeError("Cannot run FgcmCalibrateTract with " 

226 "fgcmBuildStars.checkAllCcds set to True") 

227 

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

229 camera = butler.get('camera') 

230 

231 dataRefDict = {} 

232 dataRefDict['camera'] = camera 

233 dataRefDict['source_catalogs'] = dataRefs 

234 dataRefDict['sourceSchema'] = butler.get('src_schema', immediate=True).schema 

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

236 

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

238 

239 visitDataRefName = self.config.fgcmBuildStars.visitDataRefName 

240 ccdDataRefName = self.config.fgcmBuildStars.ccdDataRefName 

241 

242 if struct.photoCalibs is not None: 

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

244 

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

246 butler.put(photoCalib, 'fgcm_tract_photoCalib', 

247 dataId={visitDataRefName: visit, 

248 ccdDataRefName: detector, 

249 'filter': physicalFilter, 

250 'tract': tract}) 

251 

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

253 

254 if struct.atmospheres is not None: 

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

256 for visit, atm in struct.atmospheres: 

257 butler.put(atm, "transmission_atmosphere_fgcm_tract", 

258 dataId={visitDataRefName: visit, 

259 'tract': tract}) 

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

261 

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

263 

264 def run(self, dataRefDict, tract, 

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

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

267 

268 Parameters 

269 ---------- 

270 dataRefDict : `dict` 

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

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

273 dataRef dictionary with the following keys. Note that all 

274 keys need not be set based on config parameters. 

275 

276 ``"camera"`` 

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

278 ``"source_catalogs"`` 

279 `list` of dataRefs for input source catalogs. 

280 ``"sourceSchema"`` 

281 Schema for the source catalogs. 

282 ``"fgcmLookUpTable"`` 

283 dataRef for the FGCM look-up table. 

284 ``"calexps"`` 

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

286 ``"fgcmPhotoCalibs"`` 

287 `dict` of output photoCalib dataRefs. Key is 

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

289 Present if doZeropointOutput is True. 

290 ``"fgcmTransmissionAtmospheres"`` 

291 `dict` of output atmosphere transmission dataRefs. 

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

293 Present if doAtmosphereOutput is True. 

294 tract : `int` 

295 Tract number 

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

297 Reference object loader object for fgcmBuildStars. 

298 returnCatalogs : `bool`, optional 

299 Return photoCalibs as per-visit exposure catalogs. 

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

301 Gen2 butler used for reference star outputs 

302 

303 Returns 

304 ------- 

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

306 Output structure with keys: 

307 

308 offsets : `np.ndarray` 

309 Final reference offsets, per band. 

310 repeatability : `np.ndarray` 

311 Raw fgcm repeatability for bright stars, per band. 

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

313 Generator that returns (visit, transmissionCurve) tuples. 

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

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

316 (returned if returnCatalogs is False). 

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

318 Generator that returns (visit, exposureCatalog) tuples. 

319 (returned if returnCatalogs is True). 

320 """ 

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

322 

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

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

325 calibFluxApertureRadius = None 

326 if self.config.fgcmBuildStars.doSubtractLocalBackground: 

327 try: 

328 field = self.config.fgcmBuildStars.instFluxField 

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

330 field) 

331 except RuntimeError: 

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

333 "Cannot use doSubtractLocalBackground." % 

334 (field)) 

335 

336 # Run the build stars tasks 

337 

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

339 if isinstance(butler, dafPersist.Butler): 

340 # Gen2 

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

342 dataRefDict['source_catalogs']) 

343 else: 

344 # Gen3 

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

346 dataRefDict['visitSummaryDataRefDict']) 

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

348 rad = calibFluxApertureRadius 

349 fgcmStarObservationCat = self.fgcmBuildStars.fgcmMakeAllStarObservations(groupedDataRefs, 

350 visitCat, 

351 dataRefDict['sourceSchema'], 

352 dataRefDict['camera'], 

353 calibFluxApertureRadius=rad) 

354 

355 if self.fgcmBuildStars.config.doReferenceMatches: 

356 lutDataRef = dataRefDict['fgcmLookUpTable'] 

357 if buildStarsRefObjLoader is not None: 

358 self.fgcmBuildStars.makeSubtask("fgcmLoadReferenceCatalog", 

359 refObjLoader=buildStarsRefObjLoader) 

360 else: 

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

362 else: 

363 lutDataRef = None 

364 

365 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = \ 

366 self.fgcmBuildStars.fgcmMatchStars(visitCat, 

367 fgcmStarObservationCat, 

368 lutDataRef=lutDataRef) 

369 

370 # Load the LUT 

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

372 fgcmLut, lutIndexVals, lutStd = translateFgcmLut(lutCat, 

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

374 del lutCat 

375 

376 # Translate the visit catalog into fgcm format 

377 fgcmExpInfo = translateVisitCatalog(visitCat) 

378 

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

380 self.config.fgcmFitCycle.maxIterBeforeFinalCycle, 

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

382 tract=tract) 

383 

384 # Use the first orientation. 

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

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

387 

388 # Set up the fit cycle task 

389 

390 noFitsDict = {'lutIndex': lutIndexVals, 

391 'lutStd': lutStd, 

392 'expInfo': fgcmExpInfo, 

393 'ccdOffsets': ccdOffsets} 

394 

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

396 noFitsDict=noFitsDict, noOutput=True) 

397 

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

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

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

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

402 

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

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

405 fgcmLut, 

406 fgcmExpInfo) 

407 

408 # Match star observations to visits 

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

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

411 

412 obsIndex = fgcmStarIndicesCat['obsIndex'] 

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

414 fgcmStarObservationCat['visit'][obsIndex]) 

415 

416 refMag, refMagErr = extractReferenceMags(fgcmRefCat, 

417 self.config.fgcmFitCycle.bands, 

418 self.config.fgcmFitCycle.physicalFilterMap) 

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

420 

421 fgcmStars = fgcm.FgcmStars(fgcmFitCycle.fgcmConfig) 

422 fgcmStars.loadStars(fgcmPars, 

423 fgcmStarObservationCat['visit'][obsIndex], 

424 fgcmStarObservationCat['ccd'][obsIndex], 

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

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

427 fgcmStarObservationCat['instMag'][obsIndex], 

428 fgcmStarObservationCat['instMagErr'][obsIndex], 

429 fgcmExpInfo['FILTERNAME'][visitIndex], 

430 fgcmStarIdCat['fgcm_id'][:], 

431 fgcmStarIdCat['ra'][:], 

432 fgcmStarIdCat['dec'][:], 

433 fgcmStarIdCat['obsArrIndex'][:], 

434 fgcmStarIdCat['nObs'][:], 

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

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

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

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

439 refID=refId, 

440 refMag=refMag, 

441 refMagErr=refMagErr, 

442 flagID=None, 

443 flagFlag=None, 

444 computeNobs=True) 

445 

446 # Clear out some memory 

447 del fgcmStarIdCat 

448 del fgcmStarIndicesCat 

449 del fgcmRefCat 

450 

451 fgcmFitCycle.setLUT(fgcmLut) 

452 fgcmFitCycle.setStars(fgcmStars, fgcmPars) 

453 

454 converged = False 

455 cycleNumber = 0 

456 

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

458 previousParInfo = None 

459 previousParams = None 

460 previousSuperStar = None 

461 

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

463 

464 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber) 

465 

466 if cycleNumber > 0: 

467 # Use parameters from previous cycle 

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

469 fgcmExpInfo, 

470 previousParInfo, 

471 previousParams, 

472 previousSuperStar) 

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

474 # cycle 

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

476 fgcmStarObservationCat['instMagErr'][obsIndex]) 

477 fgcmFitCycle.initialCycle = False 

478 

479 fgcmFitCycle.setPars(fgcmPars) 

480 fgcmFitCycle.finishSetup() 

481 

482 fgcmFitCycle.run() 

483 

484 # Grab the parameters for the next cycle 

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

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

487 

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

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

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

491 continue 

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

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

494 

495 # Check for convergence 

496 if np.alltrue((previousReservedRawRepeatability 

497 - fgcmFitCycle.fgcmPars.compReservedRawRepeatability) 

498 < self.config.convergenceTolerance): 

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

500 converged = True 

501 else: 

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

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

504 fgcmFitCycle.fgcmConfig.precomputeSuperStarInitialCycle = False 

505 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False 

506 previousReservedRawRepeatability[:] = fgcmFitCycle.fgcmPars.compReservedRawRepeatability 

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

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

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

510 continue 

511 cut = fgcmFitCycle.updatedPhotometricCut[i] * 1000.0 

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

513 

514 cycleNumber += 1 

515 

516 # Log warning if not converged 

517 if not converged: 

518 self.log.warning("Maximum number of fit cycles exceeded (%d) without convergence.", cycleNumber) 

519 

520 # Do final clean-up iteration 

521 fgcmFitCycle.fgcmConfig.freezeStdAtmosphere = False 

522 fgcmFitCycle.fgcmConfig.resetParameters = False 

523 fgcmFitCycle.fgcmConfig.maxIter = 0 

524 fgcmFitCycle.fgcmConfig.outputZeropoints = True 

525 fgcmFitCycle.fgcmConfig.outputStandards = True 

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

527 fgcmFitCycle.fgcmConfig.updateCycleNumber(cycleNumber) 

528 fgcmFitCycle.initialCycle = False 

529 

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

531 fgcmExpInfo, 

532 previousParInfo, 

533 previousParams, 

534 previousSuperStar) 

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

536 fgcmStarObservationCat['instMagErr'][obsIndex]) 

537 fgcmFitCycle.setPars(fgcmPars) 

538 fgcmFitCycle.finishSetup() 

539 

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

541 fgcmFitCycle.run() 

542 

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

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

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

546 continue 

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

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

549 

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

551 

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

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

554 

555 zptSchema = makeZptSchema(superStarChebSize, zptChebSize) 

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

557 

558 atmSchema = makeAtmSchema() 

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

560 

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

562 stdSchema = makeStdSchema(len(goodBands)) 

563 stdCat = makeStdCat(stdSchema, stdStruct, goodBands) 

564 

565 outStruct = self.fgcmOutputProducts.generateTractOutputProducts(dataRefDict, 

566 tract, 

567 visitCat, 

568 zptCat, atmCat, stdCat, 

569 self.config.fgcmBuildStars, 

570 returnCatalogs=returnCatalogs, 

571 butler=butler) 

572 

573 outStruct.repeatability = fgcmFitCycle.fgcmPars.compReservedRawRepeatability 

574 

575 fgcmFitCycle.freeSharedMemory() 

576 

577 return outStruct