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

1from __future__ import print_function 

2from builtins import object 

3import os 

4import numpy as np 

5import numpy.ma as ma 

6import matplotlib.pyplot as plt 

7from collections import OrderedDict 

8 

9import lsst.sims.maf.db as db 

10import lsst.sims.maf.utils as utils 

11from lsst.sims.maf.plots import PlotHandler 

12import lsst.sims.maf.maps as maps 

13from lsst.sims.maf.stackers import BaseDitherStacker 

14from .metricBundle import MetricBundle, createEmptyMetricBundle 

15import warnings 

16 

17__all__ = ['makeBundlesDictFromList', 'MetricBundleGroup'] 

18 

19 

20def makeBundlesDictFromList(bundleList): 

21 """Utility to convert a list of MetricBundles into a dictionary, keyed by the fileRoot names. 

22 

23 Raises an exception if the fileroot duplicates another metricBundle. 

24 (Note this should alert to potential cases of filename duplication). 

25 

26 Parameters 

27 ---------- 

28 bundleList : list of MetricBundles 

29 """ 

30 bDict = {} 

31 for b in bundleList: 

32 if b.fileRoot in bDict: 

33 raise NameError('More than one metricBundle is using the same fileroot, %s' % (b.fileRoot)) 

34 bDict[b.fileRoot] = b 

35 return bDict 

36 

37 

38class MetricBundleGroup(object): 

39 """The MetricBundleGroup exists to calculate the metric values for a group of 

40 MetricBundles. 

41 

42 The MetricBundleGroup will query data from a single database table (for multiple 

43 constraints), use that data to calculate metric values for multiple slicers, 

44 and calculate summary statistics and generate plots for all metrics included in 

45 the dictionary passed to the MetricBundleGroup. 

46 

47 We calculate the metric values here, rather than in the individual MetricBundles, 

48 because it is much more efficient to step through a slicer once (and calculate all 

49 the relevant metric values at each point) than it is to repeat this process multiple times. 

50 

51 The MetricBundleGroup also determines how to efficiently group the MetricBundles 

52 to reduce the number of sql queries of the database, grabbing larger chunks of data at once. 

53 

54 Parameters 

55 ---------- 

56 bundleDict : dict of MetricBundles 

57 Individual MetricBundles should be placed into a dictionary, and then passed to 

58 the MetricBundleGroup. The dictionary keys can then be used to identify MetricBundles 

59 if needed -- and to identify new MetricBundles which could be created if 'reduce' 

60 functions are run on a particular MetricBundle. 

61 A bundleDict can be conveniently created from a list of MetricBundles using 

62 makeBundlesDictFromList 

63 dbObj : Database 

64 The database object (typically an :class:`OpsimDatabase`) connected to the data to be used to 

65 calculate metrics. 

66 Advanced use: It is possible to set this to None, in which case data should be passed 

67 directly to the runCurrent method (and runAll should not be used). 

68 outDir : str, opt 

69 Directory to save the metric results. Default is the current directory. 

70 resultsDb : ResultsDb, opt 

71 A results database. If not specified, one will be created in the outDir. 

72 This database saves information about the metrics calculated, including their summary statistics. 

73 verbose : bool, opt 

74 Flag to turn on/off verbose feedback. 

75 saveEarly : bool, opt 

76 If True, metric values will be saved immediately after they are first calculated (to prevent 

77 data loss) as well as after summary statistics are calculated. 

78 If False, metric values will only be saved after summary statistics are calculated. 

79 dbTable : str, opt 

80 The name of the table in the dbObj to query for data. 

81 """ 

82 def __init__(self, bundleDict, dbObj, outDir='.', resultsDb=None, verbose=True, 

83 saveEarly=True, dbTable=None): 

84 """Set up the MetricBundleGroup. 

85 """ 

86 # Print occasional messages to screen. 

87 self.verbose = verbose 

88 # Save metric results as soon as possible (in case of crash). 

89 self.saveEarly = saveEarly 

90 # Check for output directory, create it if needed. 

91 self.outDir = outDir 

92 if not os.path.isdir(self.outDir): 

93 os.makedirs(self.outDir) 

94 

95 # Do some type checking on the MetricBundle dictionary. 

96 if not isinstance(bundleDict, dict): 

97 raise ValueError('bundleDict should be a dictionary containing MetricBundle objects.') 

98 for b in bundleDict.values(): 

99 if not isinstance(b, MetricBundle): 

100 raise ValueError('bundleDict should contain only MetricBundle objects.') 

101 # Identify the series of constraints. 

102 self.constraints = list(set([b.constraint for b in bundleDict.values()])) 

103 # Set the bundleDict (all bundles, with all constraints) 

104 self.bundleDict = bundleDict 

105 

106 # Check the dbObj. 

107 if not isinstance(dbObj, db.Database): 

108 warnings.warn('Warning: dbObj should be an instantiated Database (or child) object.') 

109 self.dbObj = dbObj 

110 # Set the table we're going to be querying. 

111 self.dbTable = dbTable 

112 if self.dbTable is None and self.dbObj is not None: 

113 self.dbTable = self.dbObj.defaultTable 

114 

115 # Check the resultsDb (optional). 

116 if resultsDb is not None: 

117 if not isinstance(resultsDb, db.ResultsDb): 

118 raise ValueError('resultsDb should be an ResultsDb object') 

119 self.resultsDb = resultsDb 

120 

121 # Dict to keep track of what's been run: 

122 self.hasRun = {} 

123 for bk in bundleDict: 

124 self.hasRun[bk] = False 

125 

126 def _checkCompatible(self, metricBundle1, metricBundle2): 

127 """Check if two MetricBundles are "compatible". 

128 Compatible indicates that the sql constraints, the slicers, and the maps are the same, and 

129 that the stackers do not interfere with each other 

130 (i.e. are not trying to set the same column in different ways). 

131 Returns True if the MetricBundles are compatible, False if not. 

132 

133 Parameters 

134 ---------- 

135 metricBundle1 : MetricBundle 

136 metricBundle2 : MetricBundle 

137 

138 Returns 

139 ------- 

140 bool 

141 """ 

142 if metricBundle1.constraint != metricBundle2.constraint: 

143 return False 

144 if metricBundle1.slicer != metricBundle2.slicer: 

145 return False 

146 if metricBundle1.mapsList.sort() != metricBundle2.mapsList.sort(): 

147 return False 

148 for stacker in metricBundle1.stackerList: 

149 for stacker2 in metricBundle2.stackerList: 

150 # If the stackers have different names, that's OK, and if they are identical, that's ok. 

151 if (stacker.__class__.__name__ == stacker2.__class__.__name__) & (stacker != stacker2): 

152 return False 

153 # But if we got this far, everything matches. 

154 return True 

155 

156 def _findCompatibleLists(self): 

157 """Find sets of compatible metricBundles from the currentBundleDict. 

158 """ 

159 # CompatibleLists stores a list of lists; 

160 # each (nested) list contains the bundleDict _keys_ of a compatible set of metricBundles. 

161 # 

162 compatibleLists = [] 

163 for k, b in self.currentBundleDict.items(): 

164 foundCompatible = False 

165 for compatibleList in compatibleLists: 

166 comparisonMetricBundleKey = compatibleList[0] 

167 compatible = self._checkCompatible(self.bundleDict[comparisonMetricBundleKey], b) 

168 if compatible: 

169 # Must compare all metricBundles in each subset (if they are a potential match), 

170 # as the stackers could be different (and one could be incompatible, 

171 # not necessarily the first) 

172 for comparisonMetricBundleKey in compatibleList[1:]: 

173 compatible = self._checkCompatible(self.bundleDict[comparisonMetricBundleKey], b) 

174 if not compatible: 

175 # If we find one which is not compatible, stop and go on to the 

176 # next subset list. 

177 break 

178 # Otherwise, we reached the end of the subset and they were all compatible. 

179 foundCompatible = True 

180 compatibleList.append(k) 

181 if not foundCompatible: 

182 # Didn't find a pre-existing compatible set; make a new one. 

183 compatibleLists.append([k, ]) 

184 self.compatibleLists = compatibleLists 

185 

186 def getData(self, constraint): 

187 """Query the data from the database. 

188 

189 The currently bundleDict should generally be set before calling getData (using setCurrent). 

190 

191 Parameters 

192 ---------- 

193 constraint : str 

194 The constraint for the currently active set of MetricBundles. 

195 """ 

196 if self.verbose: 

197 if constraint == '': 

198 print("Querying database with no constraint.") 

199 else: 

200 print("Querying database with constraint %s" % (constraint)) 

201 # Note that we do NOT run the stackers at this point (this must be done in each 'compatible' group). 

202 if self.dbTable != 'Summary': 

203 distinctExpMJD = False 

204 groupBy = None 

205 else: 

206 distinctExpMJD = True 

207 groupBy = 'expMJD' 

208 self.simData = utils.getSimData(self.dbObj, constraint, self.dbCols, 

209 tableName=self.dbTable, distinctExpMJD=distinctExpMJD, 

210 groupBy=groupBy) 

211 

212 if self.verbose: 

213 print("Found %i visits" % (self.simData.size)) 

214 

215 # Query for the fieldData if we need it for the opsimFieldSlicer. 

216 needFields = [b.slicer.needsFields for b in self.currentBundleDict.values()] 

217 if True in needFields: 

218 self.fieldData = utils.getFieldData(self.dbObj, constraint) 

219 else: 

220 self.fieldData = None 

221 

222 def runAll(self, clearMemory=False, plotNow=False, plotKwargs=None): 

223 """Runs all the metricBundles in the metricBundleGroup, over all constraints. 

224 

225 Calculates metric values, then runs reduce functions and summary statistics for 

226 all MetricBundles. 

227 

228 Parameters 

229 ---------- 

230 clearMemory : bool, opt 

231 If True, deletes metric values from memory after running each constraint group. 

232 plotNow : bool, opt 

233 If True, plots the metric values immediately after calculation. 

234 plotKwargs : bool, opt 

235 kwargs to pass to plotCurrent. 

236 """ 

237 for constraint in self.constraints: 

238 # Set the 'currentBundleDict' which is a dictionary of the metricBundles which match this 

239 # constraint. 

240 self.setCurrent(constraint) 

241 self.runCurrent(constraint, clearMemory=clearMemory, 

242 plotNow=plotNow, plotKwargs=plotKwargs) 

243 

244 def setCurrent(self, constraint): 

245 """Utility to set the currentBundleDict (i.e. a set of metricBundles with the same SQL constraint). 

246 

247 Parameters 

248 ---------- 

249 constraint : str 

250 The subset of MetricBundles with metricBundle.constraint == constraint will be 

251 included in a subset identified as the currentBundleDict. 

252 These are the active metrics to be calculated and plotted, etc. 

253 """ 

254 if constraint is None: 

255 constraint = '' 

256 self.currentBundleDict = {} 

257 for k, b in self.bundleDict.items(): 

258 if b.constraint == constraint: 

259 self.currentBundleDict[k] = b 

260 

261 def runCurrent(self, constraint, simData=None, clearMemory=False, plotNow=False, plotKwargs=None): 

262 """Run all the metricBundles which match this constraint in the metricBundleGroup. 

263 

264 Calculates the metric values, then runs reduce functions and summary statistics for 

265 metrics in the current set only (see self.setCurrent). 

266 

267 Parameters 

268 ---------- 

269 constraint : str 

270 constraint to use to set the currently active metrics 

271 simData : numpy.ndarray, opt 

272 If simData is not None, then this numpy structured array is used instead of querying 

273 data from the dbObj. 

274 clearMemory : bool, opt 

275 If True, metric values are deleted from memory after they are calculated (and saved to disk). 

276 plotNow : bool, opt 

277 Plot immediately after calculating metric values (instead of the usual procedure, which 

278 is to plot after metric values are calculated for all constraints). 

279 plotKwargs : kwargs, opt 

280 Plotting kwargs to pass to plotCurrent. 

281 """ 

282 # Build list of all the columns needed from the database. 

283 self.dbCols = [] 

284 for b in self.currentBundleDict.values(): 

285 self.dbCols.extend(b.dbCols) 

286 self.dbCols = list(set(self.dbCols)) 

287 

288 # Can pass simData directly (if had other method for getting data) 

289 if simData is not None: 

290 self.simData = simData 

291 

292 else: 

293 self.simData = None 

294 # Query for the data. 

295 try: 

296 self.getData(constraint) 

297 except UserWarning: 

298 warnings.warn('No data matching constraint %s' % constraint) 

299 metricsSkipped = [] 

300 for b in self.currentBundleDict.values(): 

301 metricsSkipped.append("%s : %s : %s" % (b.metric.name, b.metadata, b.slicer.slicerName)) 

302 warnings.warn(' This means skipping metrics %s' % metricsSkipped) 

303 return 

304 except ValueError: 

305 warnings.warn('One or more of the columns requested from the database was not available.' + 

306 ' Skipping constraint %s' % constraint) 

307 metricsSkipped = [] 

308 for b in self.currentBundleDict.values(): 

309 metricsSkipped.append("%s : %s : %s" % (b.metric.name, b.metadata, b.slicer.slicerName)) 

310 warnings.warn(' This means skipping metrics %s' % metricsSkipped) 

311 return 

312 

313 # Find compatible subsets of the MetricBundle dictionary, 

314 # which can be run/metrics calculated/ together. 

315 self._findCompatibleLists() 

316 

317 for compatibleList in self.compatibleLists: 

318 if self.verbose: 

319 print('Running: ', compatibleList) 

320 self._runCompatible(compatibleList) 

321 if self.verbose: 

322 print('Completed metric generation.') 

323 for key in compatibleList: 

324 self.hasRun[key] = True 

325 # Run the reduce methods. 

326 if self.verbose: 

327 print('Running reduce methods.') 

328 self.reduceCurrent() 

329 # Run the summary statistics. 

330 if self.verbose: 

331 print('Running summary statistics.') 

332 self.summaryCurrent() 

333 if self.verbose: 

334 print('Completed.') 

335 if plotNow: 

336 if plotKwargs is None: 

337 self.plotCurrent() 

338 else: 

339 self.plotCurrent(**plotKwargs) 

340 # Optionally: clear results from memory. 

341 if clearMemory: 

342 for b in self.currentBundleDict.values(): 

343 b.metricValues = None 

344 if self.verbose: 

345 print('Deleted metricValues from memory.') 

346 

347 

348 def getData(self, constraint): 

349 """Query the data from the database. 

350 

351 The currently bundleDict should generally be set before calling getData (using setCurrent). 

352 

353 Parameters 

354 ---------- 

355 constraint : str 

356 The constraint for the currently active set of MetricBundles. 

357 """ 

358 if self.verbose: 

359 if constraint == '': 

360 print("Querying database %s with no constraint for columns %s." % 

361 (self.dbTable, self.dbCols)) 

362 else: 

363 print("Querying database %s with constraint %s for columns %s" % 

364 (self.dbTable, constraint, self.dbCols)) 

365 # Note that we do NOT run the stackers at this point (this must be done in each 'compatible' group). 

366 self.simData = utils.getSimData(self.dbObj, constraint, self.dbCols, 

367 groupBy='default', tableName=self.dbTable) 

368 

369 if self.verbose: 

370 print("Found %i visits" % (self.simData.size)) 

371 

372 # Query for the fieldData if we need it for the opsimFieldSlicer. 

373 needFields = [b.slicer.needsFields for b in self.currentBundleDict.values()] 

374 if True in needFields: 

375 self.fieldData = utils.getFieldData(self.dbObj, constraint) 

376 else: 

377 self.fieldData = None 

378 

379 

380 def _runCompatible(self, compatibleList): 

381 """Runs a set of 'compatible' metricbundles in the MetricBundleGroup dictionary, 

382 identified by 'compatibleList' keys. 

383 

384 A compatible list of MetricBundles is a subset of the currentBundleDict. 

385 The currentBundleDict == set of MetricBundles with the same constraint. 

386 The compatibleBundles == set of MetricBundles with the same constraint, the same 

387 slicer, the same maps applied to the slicer, and stackers which do not clobber each other's data. 

388 

389 This is where the work of calculating the metric values is done. 

390 """ 

391 

392 if len(self.simData) == 0: 

393 return 

394 

395 # Grab a dictionary representation of this subset of the dictionary, for easier iteration. 

396 bDict = {key: self.currentBundleDict.get(key) for key in compatibleList} 

397 

398 # Find the unique stackers and maps. These are already "compatible" (as id'd by compatibleList). 

399 uniqStackers = [] 

400 allStackers = [] 

401 uniqMaps = [] 

402 allMaps = [] 

403 for b in bDict.values(): 

404 allStackers += b.stackerList 

405 allMaps += b.mapsList 

406 for s in allStackers: 

407 if s not in uniqStackers: 

408 uniqStackers.append(s) 

409 for m in allMaps: 

410 if m not in uniqMaps: 

411 uniqMaps.append(m) 

412 

413 # Run stackers. 

414 # Run dither stackers first. (this is a bit of a hack -- we should probably figure out 

415 # proper hierarchy and DAG so that stackers run in the order they need to. This will catch 90%). 

416 ditherStackers = [] 

417 for s in uniqStackers: 

418 if isinstance(s, BaseDitherStacker): 

419 ditherStackers.append(s) 

420 for stacker in ditherStackers: 

421 self.simData = stacker.run(self.simData, override=True) 

422 uniqStackers.remove(stacker) 

423 

424 for stacker in uniqStackers: 

425 # Note that stackers will clobber previously existing rows with the same name. 

426 self.simData = stacker.run(self.simData, override=True) 

427 

428 # Pull out one of the slicers to use as our 'slicer'. 

429 # This will be forced back into all of the metricBundles at the end (so that they track 

430 # the same metadata such as the slicePoints, in case the same actual object wasn't used). 

431 slicer = list(bDict.values())[0].slicer 

432 if (slicer.slicerName == 'OpsimFieldSlicer'): 

433 slicer.setupSlicer(self.simData, self.fieldData, maps=uniqMaps) 

434 else: 

435 slicer.setupSlicer(self.simData, maps=uniqMaps) 

436 # Copy the slicer (after setup) back into the individual metricBundles. 

437 if slicer.slicerName != 'HealpixSlicer' or slicer.slicerName != 'UniSlicer': 

438 for b in bDict.values(): 

439 b.slicer = slicer 

440 

441 # Set up (masked) arrays to store metric data in each metricBundle. 

442 for b in bDict.values(): 

443 b._setupMetricValues() 

444 

445 # Set up an ordered dictionary to be the cache if needed: 

446 # (Currently using OrderedDict, it might be faster to use 2 regular Dicts instead) 

447 if slicer.cacheSize > 0: 

448 cacheDict = OrderedDict() 

449 cache = True 

450 else: 

451 cache = False 

452 # Run through all slicepoints and calculate metrics. 

453 for i, slice_i in enumerate(slicer): 

454 slicedata = self.simData[slice_i['idxs']] 

455 if len(slicedata) == 0: 

456 # No data at this slicepoint. Mask data values. 

457 for b in bDict.values(): 

458 b.metricValues.mask[i] = True 

459 else: 

460 # There is data! Should we use our data cache? 

461 if cache: 

462 # Make the data idxs hashable. 

463 cacheKey = frozenset(slice_i['idxs']) 

464 # If key exists, set flag to use it, otherwise add it 

465 if cacheKey in cacheDict: 

466 useCache = True 

467 cacheVal = cacheDict[cacheKey] 

468 # Move this value to the end of the OrderedDict 

469 del cacheDict[cacheKey] 

470 cacheDict[cacheKey] = cacheVal 

471 else: 

472 cacheDict[cacheKey] = i 

473 useCache = False 

474 for b in bDict.values(): 

475 if useCache: 

476 b.metricValues.data[i] = b.metricValues.data[cacheDict[cacheKey]] 

477 else: 

478 b.metricValues.data[i] = b.metric.run(slicedata, slicePoint=slice_i['slicePoint']) 

479 # If we are above the cache size, drop the oldest element from the cache dict. 

480 if len(cacheDict) > slicer.cacheSize: 

481 del cacheDict[list(cacheDict.keys())[0]] 

482 

483 # Not using memoize, just calculate things normally 

484 else: 

485 for b in bDict.values(): 

486 b.metricValues.data[i] = b.metric.run(slicedata, slicePoint=slice_i['slicePoint']) 

487 # Mask data where metrics could not be computed (according to metric bad value). 

488 for b in bDict.values(): 

489 if b.metricValues.dtype.name == 'object': 

490 for ind, val in enumerate(b.metricValues.data): 

491 if val is b.metric.badval: 

492 b.metricValues.mask[ind] = True 

493 else: 

494 # For some reason, this doesn't work for dtype=object arrays. 

495 b.metricValues.mask = np.where(b.metricValues.data == b.metric.badval, 

496 True, b.metricValues.mask) 

497 

498 # Save data to disk as we go, although this won't keep summary values, etc. (just failsafe). 

499 if self.saveEarly: 

500 for b in bDict.values(): 

501 b.write(outDir=self.outDir, resultsDb=self.resultsDb) 

502 else: 

503 for b in bDict.values(): 

504 b.writeDb(resultsDb=self.resultsDb) 

505 

506 def reduceAll(self, updateSummaries=True): 

507 """Run the reduce methods for all metrics in bundleDict. 

508 

509 Running this method, for all MetricBundles at once, assumes that clearMemory was False. 

510 

511 Parameters 

512 ---------- 

513 updateSummaries : bool, opt 

514 If True, summary metrics are removed from the top-level (non-reduced) 

515 MetricBundle. Usually this should be True, as summary metrics are generally 

516 intended to run on the simpler data produced by reduce metrics. 

517 """ 

518 for constraint in self.constraints: 

519 self.setCurrent(constraint) 

520 self.reduceCurrent(updateSummaries=updateSummaries) 

521 

522 def reduceCurrent(self, updateSummaries=True): 

523 """Run all reduce functions for the metricbundle in the currently active set of MetricBundles. 

524 

525 Parameters 

526 ---------- 

527 updateSummaries : bool, opt 

528 If True, summary metrics are removed from the top-level (non-reduced) 

529 MetricBundle. Usually this should be True, as summary metrics are generally 

530 intended to run on the simpler data produced by reduce metrics. 

531 """ 

532 # Create a temporary dictionary to hold the reduced metricbundles. 

533 reduceBundleDict = {} 

534 for b in self.currentBundleDict.values(): 

535 # If there are no reduce functions associated with the metric, skip this metricBundle. 

536 if len(b.metric.reduceFuncs) > 0: 

537 # Apply reduce functions, creating a new metricBundle in the process (new metric values). 

538 for reduceFunc in b.metric.reduceFuncs.values(): 

539 newmetricbundle = b.reduceMetric(reduceFunc) 

540 # Add the new metricBundle to our metricBundleGroup dictionary. 

541 name = newmetricbundle.metric.name 

542 if name in self.bundleDict: 

543 name = newmetricbundle.fileRoot 

544 reduceBundleDict[name] = newmetricbundle 

545 if self.saveEarly: 

546 newmetricbundle.write(outDir=self.outDir, resultsDb=self.resultsDb) 

547 else: 

548 newmetricbundle.writeDb(resultsDb=self.resultsDb) 

549 # Remove summaryMetrics from top level metricbundle if desired. 

550 if updateSummaries: 

551 b.summaryMetrics = [] 

552 # Add the new metricBundles to the MetricBundleGroup dictionary. 

553 self.bundleDict.update(reduceBundleDict) 

554 # And add to to the currentBundleDict too, so we run as part of 'summaryCurrent'. 

555 self.currentBundleDict.update(reduceBundleDict) 

556 

557 def summaryAll(self): 

558 """Run the summary statistics for all metrics in bundleDict. 

559 

560 Calculating all summary statistics, for all MetricBundles, at this 

561 point assumes that clearMemory was False. 

562 """ 

563 for constraint in self.constraints: 

564 self.setCurrent(constraint) 

565 self.summaryCurrent() 

566 

567 def summaryCurrent(self): 

568 """Run summary statistics on all the metricBundles in the currently active set of MetricBundles. 

569 """ 

570 for b in self.currentBundleDict.values(): 

571 b.computeSummaryStats(self.resultsDb) 

572 

573 def plotAll(self, savefig=True, outfileSuffix=None, figformat='pdf', dpi=600, trimWhitespace=True, 

574 thumbnail=True, closefigs=True): 

575 """Generate all the plots for all the metricBundles in bundleDict. 

576 

577 Generating all ploots, for all MetricBundles, at this point, assumes that 

578 clearMemory was False. 

579 

580 Parameters 

581 ---------- 

582 savefig : bool, opt 

583 If True, save figures to disk, to self.outDir directory. 

584 outfileSuffix : bool, opt 

585 Append outfileSuffix to the end of every plot file generated. Useful for generating 

586 sequential series of images for movies. 

587 figformat : str, opt 

588 Matplotlib figure format to use to save to disk. Default pdf. 

589 dpi : int, opt 

590 DPI for matplotlib figure. Default 600. 

591 trimWhitespace : bool, opt 

592 If True, trim additional whitespace from final figures. Default True. 

593 thumbnail : bool, opt 

594 If True, save a small thumbnail jpg version of the output file to disk as well. 

595 This is useful for showMaf web pages. Default True. 

596 closefigs : bool, opt 

597 Close the matplotlib figures after they are saved to disk. If many figures are 

598 generated, closing the figures saves significant memory. Default True. 

599 """ 

600 for constraint in self.constraints: 

601 if self.verbose: 

602 print('Plotting figures with "%s" constraint now.' % (constraint)) 

603 

604 self.setCurrent(constraint) 

605 self.plotCurrent(savefig=savefig, outfileSuffix=outfileSuffix, figformat=figformat, dpi=dpi, 

606 trimWhitespace=trimWhitespace, thumbnail=thumbnail, closefigs=closefigs) 

607 

608 def plotCurrent(self, savefig=True, outfileSuffix=None, figformat='pdf', dpi=600, trimWhitespace=True, 

609 thumbnail=True, closefigs=True): 

610 """Generate the plots for the currently active set of MetricBundles. 

611 

612 Parameters 

613 ---------- 

614 savefig : bool, opt 

615 If True, save figures to disk, to self.outDir directory. 

616 outfileSuffix : str, opt 

617 Append outfileSuffix to the end of every plot file generated. Useful for generating 

618 sequential series of images for movies. 

619 figformat : str, opt 

620 Matplotlib figure format to use to save to disk. Default pdf. 

621 dpi : int, opt 

622 DPI for matplotlib figure. Default 600. 

623 trimWhitespace : bool, opt 

624 If True, trim additional whitespace from final figures. Default True. 

625 thumbnail : bool, opt 

626 If True, save a small thumbnail jpg version of the output file to disk as well. 

627 This is useful for showMaf web pages. Default True. 

628 closefigs : bool, opt 

629 Close the matplotlib figures after they are saved to disk. If many figures are 

630 generated, closing the figures saves significant memory. Default True. 

631 """ 

632 plotHandler = PlotHandler(outDir=self.outDir, resultsDb=self.resultsDb, 

633 savefig=savefig, figformat=figformat, dpi=dpi, 

634 trimWhitespace=trimWhitespace, thumbnail=thumbnail) 

635 

636 for b in self.currentBundleDict.values(): 

637 try: 

638 b.plot(plotHandler=plotHandler, outfileSuffix=outfileSuffix, savefig=savefig) 

639 except ValueError as ve: 

640 message = 'Plotting failed for metricBundle %s.' % (b.fileRoot) 

641 message += ' Error message: %s' % (ve) 

642 warnings.warn(message) 

643 if closefigs: 

644 plt.close('all') 

645 if self.verbose: 

646 print('Plotting complete.') 

647 

648 def writeAll(self): 

649 """Save all the MetricBundles to disk. 

650 

651 Saving all MetricBundles to disk at this point assumes that clearMemory was False. 

652 """ 

653 for constraint in self.constraints: 

654 self.setCurrent(constraint) 

655 self.writeCurrent() 

656 

657 def writeCurrent(self): 

658 """Save all the MetricBundles in the currently active set to disk. 

659 """ 

660 if self.verbose: 

661 if self.saveEarly: 

662 print('Re-saving metric bundles.') 

663 else: 

664 print('Saving metric bundles.') 

665 for b in self.currentBundleDict.values(): 

666 b.write(outDir=self.outDir, resultsDb=self.resultsDb) 

667 

668 def readAll(self): 

669 """Attempt to read all MetricBundles from disk. 

670 

671 You must set the metrics/slicer/constraint/runName for a metricBundle appropriately; 

672 then this method will search for files in the location self.outDir/metricBundle.fileRoot. 

673 Reads all the files associated with all metricbundles in self.bundleDict. 

674 """ 

675 reduceBundleDict = {} 

676 removeBundles = [] 

677 for b in self.bundleDict: 

678 bundle = self.bundleDict[b] 

679 filename = os.path.join(self.outDir, bundle.fileRoot + '.npz') 

680 try: 

681 # Create a temporary metricBundle to read the data into. 

682 # (we don't use b directly, as this overrides plotDict/etc). 

683 tmpBundle = createEmptyMetricBundle() 

684 tmpBundle.read(filename) 

685 # Copy the tmpBundle metricValues into bundle. 

686 bundle.metricValues = tmpBundle.metricValues 

687 # And copy the slicer into b, to get slicePoints. 

688 bundle.slicer = tmpBundle.slicer 

689 if self.verbose: 

690 print('Read %s from disk.' % (bundle.fileRoot)) 

691 except IOError: 

692 warnings.warn('Warning: file %s not found, bundle not restored.' % filename) 

693 removeBundles.append(b) 

694 

695 # Look to see if this is a complex metric, with associated 'reduce' functions, 

696 # and read those in too. 

697 if len(bundle.metric.reduceFuncs) > 0: 

698 origMetricName = bundle.metric.name 

699 for reduceFunc in bundle.metric.reduceFuncs.values(): 

700 reduceName = origMetricName + '_' + reduceFunc.__name__.replace('reduce', '') 

701 # Borrow the fileRoot in b (we'll reset it appropriately afterwards). 

702 bundle.metric.name = reduceName 

703 bundle._buildFileRoot() 

704 filename = os.path.join(self.outDir, bundle.fileRoot + '.npz') 

705 tmpBundle = createEmptyMetricBundle() 

706 try: 

707 tmpBundle.read(filename) 

708 # This won't necessarily recreate the plotDict and displayDict exactly 

709 # as they would have been made if you calculated the reduce metric from scratch. 

710 # Perhaps update these metric reduce dictionaries after reading them in? 

711 newmetricBundle = MetricBundle(metric=bundle.metric, slicer=bundle.slicer, 

712 constraint=bundle.constraint, 

713 stackerList=bundle.stackerList, runName=bundle.runName, 

714 metadata=bundle.metadata, 

715 plotDict=bundle.plotDict, 

716 displayDict=bundle.displayDict, 

717 summaryMetrics=bundle.summaryMetrics, 

718 mapsList=bundle.mapsList, 

719 fileRoot=bundle.fileRoot, plotFuncs=bundle.plotFuncs) 

720 newmetricBundle.metric.name = reduceName 

721 newmetricBundle.metricValues = ma.copy(tmpBundle.metricValues) 

722 # Add the new metricBundle to our metricBundleGroup dictionary. 

723 name = newmetricBundle.metric.name 

724 if name in self.bundleDict: 

725 name = newmetricBundle.fileRoot 

726 reduceBundleDict[name] = newmetricBundle 

727 if self.verbose: 

728 print('Read %s from disk.' % (newmetricBundle.fileRoot)) 

729 except IOError: 

730 warnings.warn('Warning: file %s not found, bundle not restored ("reduce" metric).' 

731 % filename) 

732 

733 # Remove summaryMetrics from top level metricbundle. 

734 bundle.summaryMetrics = [] 

735 # Update parent MetricBundle name. 

736 bundle.metric.name = origMetricName 

737 bundle._buildFileRoot() 

738 

739 # Add the reduce bundles into the bundleDict. 

740 self.bundleDict.update(reduceBundleDict) 

741 # And remove the bundles which were not found on disk, so we don't try to make (blank) plots. 

742 for b in removeBundles: 

743 del self.bundleDict[b] 

744