Coverage for python/lsst/sims/maf/metricBundles/metricBundleGroup.py : 6%

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
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
17__all__ = ['makeBundlesDictFromList', 'MetricBundleGroup']
20def makeBundlesDictFromList(bundleList):
21 """Utility to convert a list of MetricBundles into a dictionary, keyed by the fileRoot names.
23 Raises an exception if the fileroot duplicates another metricBundle.
24 (Note this should alert to potential cases of filename duplication).
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
38class MetricBundleGroup(object):
39 """The MetricBundleGroup exists to calculate the metric values for a group of
40 MetricBundles.
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.
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.
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.
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)
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
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
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
121 # Dict to keep track of what's been run:
122 self.hasRun = {}
123 for bk in bundleDict:
124 self.hasRun[bk] = False
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.
133 Parameters
134 ----------
135 metricBundle1 : MetricBundle
136 metricBundle2 : MetricBundle
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
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
186 def getData(self, constraint):
187 """Query the data from the database.
189 The currently bundleDict should generally be set before calling getData (using setCurrent).
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)
212 if self.verbose:
213 print("Found %i visits" % (self.simData.size))
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
222 def runAll(self, clearMemory=False, plotNow=False, plotKwargs=None):
223 """Runs all the metricBundles in the metricBundleGroup, over all constraints.
225 Calculates metric values, then runs reduce functions and summary statistics for
226 all MetricBundles.
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)
244 def setCurrent(self, constraint):
245 """Utility to set the currentBundleDict (i.e. a set of metricBundles with the same SQL constraint).
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
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.
264 Calculates the metric values, then runs reduce functions and summary statistics for
265 metrics in the current set only (see self.setCurrent).
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))
288 # Can pass simData directly (if had other method for getting data)
289 if simData is not None:
290 self.simData = simData
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
313 # Find compatible subsets of the MetricBundle dictionary,
314 # which can be run/metrics calculated/ together.
315 self._findCompatibleLists()
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.')
348 def getData(self, constraint):
349 """Query the data from the database.
351 The currently bundleDict should generally be set before calling getData (using setCurrent).
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)
369 if self.verbose:
370 print("Found %i visits" % (self.simData.size))
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
380 def _runCompatible(self, compatibleList):
381 """Runs a set of 'compatible' metricbundles in the MetricBundleGroup dictionary,
382 identified by 'compatibleList' keys.
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.
389 This is where the work of calculating the metric values is done.
390 """
392 if len(self.simData) == 0:
393 return
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}
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)
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)
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)
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
441 # Set up (masked) arrays to store metric data in each metricBundle.
442 for b in bDict.values():
443 b._setupMetricValues()
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]]
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)
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)
506 def reduceAll(self, updateSummaries=True):
507 """Run the reduce methods for all metrics in bundleDict.
509 Running this method, for all MetricBundles at once, assumes that clearMemory was False.
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)
522 def reduceCurrent(self, updateSummaries=True):
523 """Run all reduce functions for the metricbundle in the currently active set of MetricBundles.
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)
557 def summaryAll(self):
558 """Run the summary statistics for all metrics in bundleDict.
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()
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)
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.
577 Generating all ploots, for all MetricBundles, at this point, assumes that
578 clearMemory was False.
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))
604 self.setCurrent(constraint)
605 self.plotCurrent(savefig=savefig, outfileSuffix=outfileSuffix, figformat=figformat, dpi=dpi,
606 trimWhitespace=trimWhitespace, thumbnail=thumbnail, closefigs=closefigs)
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.
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)
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.')
648 def writeAll(self):
649 """Save all the MetricBundles to disk.
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()
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)
668 def readAll(self):
669 """Attempt to read all MetricBundles from disk.
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)
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)
733 # Remove summaryMetrics from top level metricbundle.
734 bundle.summaryMetrics = []
735 # Update parent MetricBundle name.
736 bundle.metric.name = origMetricName
737 bundle._buildFileRoot()
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]