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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

from builtins import zip 

from builtins import object 

import os 

from copy import deepcopy 

import numpy as np 

import numpy.ma as ma 

import warnings 

 

import lsst.sims.maf.metrics as metrics 

import lsst.sims.maf.slicers as slicers 

import lsst.sims.maf.stackers as stackers 

import lsst.sims.maf.maps as maps 

import lsst.sims.maf.plots as plots 

from lsst.sims.maf.stackers import ColInfo 

import lsst.sims.maf.utils as utils 

 

__all__ = ['MetricBundle', 'createEmptyMetricBundle'] 

 

 

def createEmptyMetricBundle(): 

"""Create an empty metric bundle. 

 

Returns 

------- 

MetricBundle 

An empty metric bundle, configured with just the :class:`BaseMetric` and :class:`BaseSlicer`. 

""" 

return MetricBundle(metrics.BaseMetric(), slicers.BaseSlicer(), '') 

 

 

class MetricBundle(object): 

"""The MetricBundle is defined by a combination of a (single) metric, slicer and 

constraint - together these define a unique combination of an opsim benchmark. 

An example would be: a CountMetric, a HealpixSlicer, and a sqlconstraint 'filter="r"'. 

 

After the metric is evaluated over the slicePoints of the slicer, the resulting 

metric values are saved in the MetricBundle. 

 

The MetricBundle also saves the summary metrics to be used to generate summary 

statistics over those metric values, as well as the resulting summary statistic values. 

 

Plotting parameters and display parameters (for showMaf) are saved in the MetricBundle, 

as well as additional metadata such as the opsim run name, and relevant stackers and maps 

to apply when calculating the metric values. 

""" 

def __init__(self, metric, slicer, constraint=None, sqlconstraint=None, 

stackerList=None, runName='opsim', metadata=None, 

plotDict=None, displayDict=None, 

summaryMetrics=None, mapsList=None, 

fileRoot=None, plotFuncs=None): 

# Set the metric. 

52 ↛ 53line 52 didn't jump to line 53, because the condition on line 52 was never true if not isinstance(metric, metrics.BaseMetric): 

raise ValueError('metric must be an lsst.sims.maf.metrics object') 

self.metric = metric 

# Set the slicer. 

56 ↛ 57line 56 didn't jump to line 57, because the condition on line 56 was never true if not isinstance(slicer, slicers.BaseSlicer): 

raise ValueError('slicer must be an lsst.sims.maf.slicers object') 

self.slicer = slicer 

# Set the constraint. 

self.constraint = constraint 

61 ↛ 63line 61 didn't jump to line 63, because the condition on line 61 was never true if self.constraint is None: 

# Provide backwards compatibility for now - phase out sqlconstraint eventually. 

if sqlconstraint is not None: 

warnings.warn('Future warning - "sqlconstraint" will be deprecated in favor of ' 

'"constraint" in a future release.') 

self.constraint = sqlconstraint 

67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true if self.constraint is None: 

self.constraint = '' 

# Set the stackerlist if applicable. 

if stackerList is not None: 

71 ↛ 72line 71 didn't jump to line 72, because the condition on line 71 was never true if isinstance(stackerList, stackers.BaseStacker): 

self.stackerList = [stackerList, ] 

else: 

self.stackerList = [] 

for s in stackerList: 

76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true if not isinstance(s, stackers.BaseStacker): 

raise ValueError('stackerList must only contain lsst.sims.maf.stackers objs') 

self.stackerList.append(s) 

else: 

self.stackerList = [] 

# Set the 'maps' to apply to the slicer, if applicable. 

82 ↛ 83line 82 didn't jump to line 83, because the condition on line 82 was never true if mapsList is not None: 

if isinstance(mapsList, maps.BaseMap): 

self.mapsList = [mapsList, ] 

else: 

self.mapsList = [] 

for m in mapsList: 

if not isinstance(m, maps.BaseMap): 

raise ValueError('mapsList must only contain lsst.sims.maf.maps objects') 

self.mapsList.append(m) 

else: 

self.mapsList = [] 

# If the metric knows it needs a particular map, add it to the list. 

mapNames = [mapName.__class__.__name__ for mapName in self.mapsList] 

95 ↛ 103line 95 didn't jump to line 103, because the condition on line 95 was never false if hasattr(self.metric, 'maps'): 

96 ↛ 97line 96 didn't jump to line 97, because the loop on line 96 never started for mapName in self.metric.maps: 

if mapName not in mapNames: 

tempMap = getattr(maps, mapName)() 

self.mapsList.append(tempMap) 

mapNames.append(mapName) 

 

# Add the summary stats, if applicable. 

self.setSummaryMetrics(summaryMetrics) 

# Set the provenance/metadata. 

self._buildMetadata(metadata) 

# Set the run name and build the output filename base (fileRoot). 

self.setRunName(runName) 

# Reset fileRoot, if provided. 

109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true if fileRoot is not None: 

self.fileRoot = fileRoot 

# Determine the columns needed from the database. 

self._findReqCols() 

# Set the plotting classes/functions. 

self.setPlotFuncs(plotFuncs) 

# Set the plotDict and displayDicts. 

self.plotDict = {} 

self.setPlotDict(plotDict) 

# Update/set displayDict. 

self.displayDict = {} 

self.setDisplayDict(displayDict) 

# This is where we store the metric values and summary stats. 

self.metricValues = None 

self.summaryValues = None 

 

def _resetMetricBundle(self): 

"""Reset all properties of MetricBundle. 

""" 

self.metric = None 

self.slicer = None 

self.constraint = None 

self.stackerList = [] 

self.summaryMetrics = [] 

self.plotFuncs = [] 

self.mapsList = None 

self.runName = 'opsim' 

self.metadata = '' 

self.dbCols = None 

self.fileRoot = None 

self.plotDict = {} 

self.displayDict = {} 

self.metricValues = None 

self.summaryValues = None 

 

def _setupMetricValues(self): 

"""Set up the numpy masked array to store the metric value data. 

""" 

dtype = self.metric.metricDtype 

# Can't store healpix slicer mask values in an int array. 

149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true if dtype == 'int': 

dtype = 'float' 

if self.metric.shape == 1: 

shape = self.slicer.shape 

else: 

shape = (self.slicer.shape, self.metric.shape) 

self.metricValues = ma.MaskedArray(data=np.empty(shape, dtype), 

mask=np.zeros(shape, 'bool'), 

fill_value=self.slicer.badval) 

 

def _buildMetadata(self, metadata): 

"""If no metadata is provided, process the constraint 

(by removing extra spaces, quotes, the word 'filter' and equal signs) to make a metadata version. 

e.g. 'filter = "r"' becomes 'r' 

""" 

164 ↛ 169line 164 didn't jump to line 169, because the condition on line 164 was never false if metadata is None: 

self.metadata = self.constraint.replace('=', '').replace('filter', '').replace("'", '') 

self.metadata = self.metadata.replace('"', '').replace(' ', ' ') 

self.metadata.strip(' ') 

else: 

self.metadata = metadata 

 

def _buildFileRoot(self): 

""" 

Build an auto-generated output filename root (i.e. minus the plot type or .npz ending). 

""" 

# Build basic version. 

self.fileRoot = '_'.join([self.runName, self.metric.name, self.metadata, 

self.slicer.slicerName[:4].upper()]) 

# Sanitize output name if needed. 

self.fileRoot = utils.nameSanitize(self.fileRoot) 

 

def _findReqCols(self): 

"""Find the columns needed by the metrics, slicers, and stackers. 

If there are any additional stackers required, instatiate them and add them to 

the self.stackers list. 

(default stackers have to be instantiated to determine what additional columns 

are needed from database). 

""" 

# Find the columns required by the metrics and slicers (including if they come from stackers). 

colInfo = ColInfo() 

dbcolnames = set() 

defaultstackers = set() 

# Look for the source for the columns for the slicer. 

for col in self.slicer.columnsNeeded: 

colsource = colInfo.getDataSource(col) 

if colsource != colInfo.defaultDataSource: 

defaultstackers.add(colsource) 

else: 

dbcolnames.add(col) 

# Look for the source of columns for this metric (only). 

# We can't use the colRegistry here because we want the columns for this metric only. 

for col in self.metric.colNameArr: 

colsource = colInfo.getDataSource(col) 

203 ↛ 204line 203 didn't jump to line 204, because the condition on line 203 was never true if colsource != colInfo.defaultDataSource: 

defaultstackers.add(colsource) 

else: 

dbcolnames.add(col) 

# Remove explicity instantiated stackers from defaultstacker set. 

for s in self.stackerList: 

209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true if s.__class__ in defaultstackers: 

defaultstackers.remove(s.__class__) 

# Instantiate and add the remaining default stackers. 

for s in defaultstackers: 

self.stackerList.append(s()) 

# Add the columns needed by all stackers to the list to grab from the database. 

for s in self.stackerList: 

for col in s.colsReq: 

dbcolnames.add(col) 

# Remove 'metricdata' from dbcols if it ended here by default. 

219 ↛ 220line 219 didn't jump to line 220, because the condition on line 219 was never true if 'metricdata' in dbcolnames: 

dbcolnames.remove('metricdata') 

self.dbCols = dbcolnames 

 

def setSummaryMetrics(self, summaryMetrics): 

"""Set (or reset) the summary metrics for the metricbundle. 

 

Parameters 

---------- 

summaryMetrics : List[BaseMetric] 

Instantiated summary metrics to use to calculate summary statistics for this metric. 

""" 

231 ↛ 232line 231 didn't jump to line 232, because the condition on line 231 was never true if summaryMetrics is not None: 

if isinstance(summaryMetrics, metrics.BaseMetric): 

self.summaryMetrics = [summaryMetrics] 

else: 

self.summaryMetrics = [] 

for s in summaryMetrics: 

if not isinstance(s, metrics.BaseMetric): 

raise ValueError('SummaryStats must only contain lsst.sims.maf.metrics objects') 

self.summaryMetrics.append(s) 

else: 

# Add identity metric to unislicer metric values (to get them into resultsDB). 

242 ↛ 243line 242 didn't jump to line 243, because the condition on line 242 was never true if self.slicer.slicerName == 'UniSlicer': 

self.summaryMetrics = [metrics.IdentityMetric()] 

else: 

self.summaryMetrics = [] 

 

def setPlotFuncs(self, plotFuncs): 

"""Set or reset the plotting functions. 

 

The default is to use all the plotFuncs associated with the slicer, which 

is what happens in self.plot if setPlotFuncs is not used to override self.plotFuncs. 

 

Parameters 

---------- 

plotFuncs : List[BasePlotter] 

The plotter or plotters to use to generate visuals for this metric. 

""" 

258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true if plotFuncs is not None: 

if plotFuncs is isinstance(plotFuncs, plots.BasePlotter): 

self.plotFuncs = [plotFuncs] 

else: 

self.plotFuncs = [] 

for pFunc in plotFuncs: 

if not isinstance(pFunc, plots.BasePlotter): 

raise ValueError('plotFuncs should contain instantiated ' + 

'lsst.sims.maf.plotter objects.') 

self.plotFuncs.append(pFunc) 

else: 

self.plotFuncs = [] 

for pFunc in self.slicer.plotFuncs: 

271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true if isinstance(pFunc, plots.BasePlotter): 

self.plotFuncs.append(pFunc) 

else: 

self.plotFuncs.append(pFunc()) 

 

def setPlotDict(self, plotDict): 

"""Set or update any property of plotDict. 

 

Parameters 

---------- 

plotDict : dict 

A dictionary of plotting parameters. 

The usable keywords vary with each lsst.sims.maf.plots Plotter. 

""" 

# Don't auto-generate anything here - the plotHandler does it. 

286 ↛ 287line 286 didn't jump to line 287, because the condition on line 286 was never true if plotDict is not None: 

self.plotDict.update(plotDict) 

# Check for bad zp or normVal values. 

289 ↛ 290line 289 didn't jump to line 290, because the condition on line 289 was never true if 'zp' in self.plotDict: 

if self.plotDict['zp'] is not None: 

if not np.isfinite(self.plotDict['zp']): 

warnings.warn('Warning! Plot zp for %s was infinite: removing zp from plotDict' 

% (self.fileRoot)) 

del self.plotDict['zp'] 

295 ↛ 296line 295 didn't jump to line 296, because the condition on line 295 was never true if 'normVal' in self.plotDict: 

if self.plotDict['normVal'] == 0: 

warnings.warn('Warning! Plot normalization value for %s was 0: removing normVal from plotDict' 

% (self.fileRoot)) 

del self.plotDict['normVal'] 

 

def setDisplayDict(self, displayDict=None, resultsDb=None): 

"""Set or update any property of displayDict. 

 

Parameters 

---------- 

displayDict : Optional[dict] 

Dictionary of display parameters for showMaf. 

Expected keywords: 'group', 'subgroup', 'order', 'caption'. 

'group', 'subgroup', and 'order' control where the metric results are shown on the showMaf page. 

'caption' provides a caption to use with the metric results. 

These values are saved in the results database. 

resultsDb : Optional[ResultsDb] 

A MAF results database, used to save the display parameters. 

""" 

# Set up a temporary dictionary with the default values. 

tmpDisplayDict = {'group': None, 'subgroup': None, 'order': 0, 'caption': None} 

# Update from self.displayDict (to use existing values, if present). 

tmpDisplayDict.update(self.displayDict) 

# And then update from any values being passed now. 

320 ↛ 321line 320 didn't jump to line 321, because the condition on line 320 was never true if displayDict is not None: 

tmpDisplayDict.update(displayDict) 

# Reset self.displayDict to this updated dictionary. 

self.displayDict = tmpDisplayDict 

# If we still need to auto-generate a caption, do it. 

325 ↛ 339line 325 didn't jump to line 339, because the condition on line 325 was never false if self.displayDict['caption'] is None: 

326 ↛ 333line 326 didn't jump to line 333, because the condition on line 326 was never false if self.metric.comment is None: 

caption = self.metric.name + ' calculated on a %s basis' % (self.slicer.slicerName) 

if self.constraint!='' and self.constraint is not None: 

caption += ' using a subset of data selected via %s.' % (self.constraint) 

else: 

caption += '.' 

else: 

caption = self.metric.comment 

334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true if 'zp' in self.plotDict: 

caption += ' Values plotted with a zeropoint of %.2f.' % (self.plotDict['zp']) 

336 ↛ 337line 336 didn't jump to line 337, because the condition on line 336 was never true if 'normVal' in self.plotDict: 

caption += ' Values plotted with a normalization value of %.2f.' % (self.plotDict['normVal']) 

self.displayDict['caption'] = caption 

339 ↛ 341line 339 didn't jump to line 341, because the condition on line 339 was never true if resultsDb: 

# Update the display values in the resultsDb. 

metricId = resultsDb.updateMetric(self.metric.name, self.slicer.slicerName, 

self.runName, self.constraint, 

self.metadata, None) 

resultsDb.updateDisplay(metricId, self.displayDict) 

 

def setRunName(self, runName, updateFileRoot=True): 

"""Set (or reset) the runName. FileRoot will be updated accordingly if desired. 

 

Parameters 

---------- 

runName: str 

Run Name, which will become part of the fileRoot. 

fileRoot: bool, opt 

Flag to update the fileRoot with the runName. Default True. 

""" 

self.runName = runName 

357 ↛ exitline 357 didn't return from function 'setRunName', because the condition on line 357 was never false if updateFileRoot: 

self._buildFileRoot() 

 

def write(self, comment='', outDir='.', outfileSuffix=None, resultsDb=None): 

"""Write metricValues (and associated metadata) to disk. 

 

Parameters 

---------- 

comment : Optional[str] 

Any additional comments to add to the output file 

outDir : Optional[str] 

The output directory 

outfileSuffix : Optional[str] 

Additional suffix to add to the output files (typically a numerical suffix for movies) 

resultsD : Optional[ResultsDb] 

Results database to store information on the file output 

""" 

374 ↛ 375line 374 didn't jump to line 375, because the condition on line 374 was never true if outfileSuffix is not None: 

outfile = self.fileRoot + '_' + outfileSuffix + '.npz' 

else: 

outfile = self.fileRoot + '.npz' 

self.slicer.writeData(os.path.join(outDir, outfile), 

self.metricValues, 

metricName=self.metric.name, 

simDataName=self.runName, 

constraint=self.constraint, 

metadata=self.metadata + comment, 

displayDict=self.displayDict, 

plotDict=self.plotDict) 

386 ↛ exitline 386 didn't return from function 'write', because the condition on line 386 was never false if resultsDb: 

metricId = resultsDb.updateMetric(self.metric.name, self.slicer.slicerName, 

self.runName, self.constraint, 

self.metadata, outfile) 

resultsDb.updateDisplay(metricId, self.displayDict) 

 

def outputJSON(self): 

"""Set up and call the baseSlicer outputJSON method, to output to IO string. 

 

Returns 

------- 

io 

IO object containing JSON data representing the metric bundle data. 

""" 

io = self.slicer.outputJSON(self.metricValues, 

metricName=self.metric.name, 

simDataName=self.runName, 

metadata=self.metadata, 

plotDict=self.plotDict) 

return io 

 

def read(self, filename): 

"""Read metricValues and associated metadata from disk. 

Overwrites any data currently in metricbundle. 

 

Parameters 

---------- 

filename : str 

The file from which to read the metric bundle data. 

""" 

if not os.path.isfile(filename): 

raise IOError('%s not found' % filename) 

 

self._resetMetricBundle() 

# Set up a base slicer to read data (we don't know type yet). 

baseslicer = slicers.BaseSlicer() 

# Use baseslicer to read file. 

metricValues, slicer, header = baseslicer.readData(filename) 

self.slicer = slicer 

self.metricValues = metricValues 

self.metricValues.fill_value = slicer.badval 

# It's difficult to reinstantiate the metric object, as we don't 

# know what it is necessarily -- the metricName can be changed. 

self.metric = metrics.BaseMetric() 

# But, for plot label building, we do need to try to recreate the 

# metric name and units. 

self.metric.name = header['metricName'] 

if 'plotDict' in header: 

if 'units' in header['plotDict']: 

self.metric.units = header['plotDict']['units'] 

else: 

self.metric.units = '' 

self.runName = header['simDataName'] 

try: 

self.constraint = header['constraint'] 

except KeyError: 

self.constraint = header['sqlconstraint'] 

self.metadata = header['metadata'] 

if self.metadata is None: 

self._buildMetadata() 

if 'plotDict' in header: 

self.setPlotDict(header['plotDict']) 

if 'displayDict' in header: 

self.setDisplayDict(header['displayDict']) 

path, head = os.path.split(filename) 

self.fileRoot = head.replace('.npz', '') 

self.setPlotFuncs(None) 

 

def computeSummaryStats(self, resultsDb=None): 

"""Compute summary statistics on metricValues, using summaryMetrics (metricbundle list). 

 

Parameters 

---------- 

resultsDb : Optional[ResultsDb] 

ResultsDb object to use to store the summary statistic values on disk. 

""" 

462 ↛ 464line 462 didn't jump to line 464, because the condition on line 462 was never false if self.summaryValues is None: 

self.summaryValues = {} 

464 ↛ exitline 464 didn't return from function 'computeSummaryStats', because the condition on line 464 was never false if self.summaryMetrics is not None: 

# Build array of metric values, to use for (most) summary statistics. 

rarr_std = np.array(list(zip(self.metricValues.compressed())), 

dtype=[('metricdata', self.metricValues.dtype)]) 

468 ↛ 470line 468 didn't jump to line 470, because the loop on line 468 never started for m in self.summaryMetrics: 

# The summary metric colname should already be set to 'metricdata', but in case it's not: 

m.colname = 'metricdata' 

summaryName = m.name.replace(' metricdata', '').replace(' None', '') 

if hasattr(m, 'maskVal'): 

# summary metric requests to use the mask value, as specified by itself, 

# rather than skipping masked vals. 

rarr = np.array(list(zip(self.metricValues.filled(m.maskVal))), 

dtype=[('metricdata', self.metricValues.dtype)]) 

else: 

rarr = rarr_std 

if np.size(rarr) == 0: 

summaryVal = self.slicer.badval 

else: 

summaryVal = m.run(rarr) 

self.summaryValues[summaryName] = summaryVal 

# Add summary metric info to results database, if applicable. 

if resultsDb: 

metricId = resultsDb.updateMetric(self.metric.name, self.slicer.slicerName, 

self.runName, self.constraint, self.metadata, None) 

resultsDb.updateSummaryStat(metricId, summaryName=summaryName, summaryValue=summaryVal) 

 

def reduceMetric(self, reduceFunc, reducePlotDict=None, reduceDisplayDict=None): 

"""Run 'reduceFunc' (any function that operates on self.metricValues). 

Typically reduceFunc will be the metric reduce functions, as they are tailored to expect the 

metricValues format. 

reduceDisplayDict and reducePlotDicts are displayDicts and plotDicts to be 

applied to the new metricBundle. 

 

Parameters 

---------- 

reduceFunc : Func 

Any function that will operate on self.metricValues (typically metric.reduce* function). 

reducePlotDict : Optional[dict] 

Plot dictionary for the results of the reduce function. 

reduceDisplayDict : Optional[dict] 

Display dictionary for the results of the reduce function. 

 

Returns 

------- 

MetricBundle 

New metric bundle, inheriting metadata from this metric bundle, but containing the new 

metric values calculated with the 'reduceFunc'. 

""" 

# Generate a name for the metric values processed by the reduceFunc. 

rName = reduceFunc.__name__.replace('reduce', '') 

reduceName = self.metric.name + '_' + rName 

# Set up metricBundle to store new metric values, and add plotDict/displayDict. 

newmetric = deepcopy(self.metric) 

newmetric.name = reduceName 

newmetric.metricDtype = 'float' 

if reducePlotDict is not None: 

if 'units' in reducePlotDict: 

newmetric.units = reducePlotDict['units'] 

newmetricBundle = MetricBundle(metric=newmetric, slicer=self.slicer, 

stackerList=self.stackerList, 

constraint=self.constraint, 

metadata=self.metadata, 

runName=self.runName, 

plotDict=None, plotFuncs=self.plotFuncs, 

displayDict=None, 

summaryMetrics=self.summaryMetrics, 

mapsList=self.mapsList, fileRoot='') 

# Build a new output file root name. 

newmetricBundle._buildFileRoot() 

# Add existing plotDict (except for title/xlabels etc) into new plotDict. 

for k, v in self.plotDict.items(): 

if k not in newmetricBundle.plotDict: 

newmetricBundle.plotDict[k] = v 

# Update newmetricBundle's plot dictionary with any set explicitly by reducePlotDict. 

newmetricBundle.setPlotDict(reducePlotDict) 

# Copy the parent metric's display dict into the reduce display dict. 

newmetricBundle.setDisplayDict(self.displayDict) 

# Set the reduce function display 'order' (this is set in the BaseMetric 

# by default, but can be overriden in a metric). 

order = newmetric.reduceOrder[rName] 

newmetricBundle.displayDict['order'] = order 

# And then update the newmetricBundle's display dictionary with any set 

# explicitly by reduceDisplayDict. 

newmetricBundle.setDisplayDict(reduceDisplayDict) 

# Set up new metricBundle's metricValues masked arrays, copying metricValue's mask. 

newmetricBundle.metricValues = ma.MaskedArray(data=np.empty(len(self.slicer), 'float'), 

mask=self.metricValues.mask, 

fill_value=self.slicer.badval) 

# Fill the reduced metric data using the reduce function. 

for i, (mVal, mMask) in enumerate(zip(self.metricValues.data, self.metricValues.mask)): 

if not mMask: 

newmetricBundle.metricValues.data[i] = reduceFunc(mVal) 

return newmetricBundle 

 

def plot(self, plotHandler=None, plotFunc=None, outfileSuffix=None, savefig=False): 

""" 

Create all plots available from the slicer. plotHandler holds the output directory info, etc. 

 

Parameters 

---------- 

plotHandler : Optional[PlotHandler] 

The plotHandler saves the output location and resultsDb connection for a set of plots. 

plotFunc : Optional[BasePlotter] 

Any plotter function. If not specified, the plotters in self.plotFuncs will be used. 

outfileSuffix : Optional[str] 

Optional string to append to the end of the plot output files. 

Useful when creating sequences of images for movies. 

savefig : Optional[bool] 

Flag indicating whether or not to save the figure to disk. Default is False. 

 

Returns 

------- 

dict 

Dictionary of plotType:figure number key/value pairs, indicating what plots were created 

and what matplotlib figure numbers were used. 

""" 

# Generate a plotHandler if none was set. 

581 ↛ 582line 581 didn't jump to line 582, because the condition on line 581 was never true if plotHandler is None: 

plotHandler = plots.PlotHandler(savefig=savefig) 

# Make plots. 

584 ↛ 585line 584 didn't jump to line 585, because the condition on line 584 was never true if plotFunc is not None: 

if isinstance(plotFunc, plots.BasePlotter): 

plotFunc = plotFunc 

else: 

plotFunc = plotFunc() 

 

plotHandler.setMetricBundles([self]) 

plotHandler.setPlotDicts(plotDicts=[self.plotDict], reset=True) 

madePlots = {} 

593 ↛ 594line 593 didn't jump to line 594, because the condition on line 593 was never true if plotFunc is not None: 

fignum = plotHandler.plot(plotFunc, outfileSuffix=outfileSuffix) 

madePlots[plotFunc.plotType] = fignum 

else: 

for plotFunc in self.plotFuncs: 

fignum = plotHandler.plot(plotFunc, outfileSuffix=outfileSuffix) 

madePlots[plotFunc.plotType] = fignum 

return madePlots