Coverage for src/CSET/operators/_colormaps.py: 98%

243 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-04 08:32 +0000

1# © Crown copyright, Met Office (2022-2026) and CSET contributors. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Functions to support colormap settings for CSET plots.""" 

16 

17import functools 

18import importlib.resources 

19import itertools 

20import json 

21import logging 

22from typing import Literal 

23 

24import iris 

25import iris.cube 

26import matplotlib as mpl 

27import matplotlib.colors as mcolors 

28import matplotlib.pyplot as plt 

29import numpy as np 

30 

31from CSET._common import ( 

32 combine_dicts, 

33 get_recipe_metadata, 

34 iter_maybe, 

35) 

36 

37logger = logging.getLogger(__name__) 

38 

39DEFAULT_DISCRETE_COLORS = mpl.colormaps["tab10"].colors + mpl.colormaps["Accent"].colors 

40 

41 

42@functools.cache 

43def load_colorbar_map(user_colorbar_file: str | None = None) -> dict: 

44 """Load the colorbar definitions from a file. 

45 

46 This is a separate function to make it cacheable. 

47 """ 

48 colorbar_file = importlib.resources.files().joinpath("_colorbar_definition.json") 

49 with open(colorbar_file, "rt", encoding="UTF-8") as fp: 

50 colorbar = json.load(fp) 

51 

52 logger.debug("User colour bar file: %s", user_colorbar_file) 

53 override_colorbar = {} 

54 if user_colorbar_file: 

55 try: 

56 with open(user_colorbar_file, "rt", encoding="UTF-8") as fp: 

57 override_colorbar = json.load(fp) 

58 except FileNotFoundError: 

59 logger.warning("Colorbar file does not exist. Using default values.") 

60 

61 # Overwrite values with the user supplied colorbar definition. 

62 colorbar = combine_dicts(colorbar, override_colorbar) 

63 return colorbar 

64 

65 

66def get_model_colors_map(cubes: iris.cube.CubeList | iris.cube.Cube) -> dict: 

67 """Get an appropriate colors for model lines in line plots. 

68 

69 For each model in the list of cubes colors either from user provided 

70 color definition file (so-called style file) or from default colors are mapped 

71 to model_name attribute. 

72 

73 Parameters 

74 ---------- 

75 cubes: CubeList or Cube 

76 Cubes with model_name attribute 

77 

78 Returns 

79 ------- 

80 model_colors_map: 

81 Dictionary mapping model_name attribute to colors 

82 """ 

83 user_colorbar_file = get_recipe_metadata().get("style_file_path", None) 

84 colorbar = load_colorbar_map(user_colorbar_file) 

85 model_names = sorted( 

86 filter( 

87 lambda x: x is not None, 

88 (cube.attributes.get("model_name", None) for cube in iter_maybe(cubes)), 

89 ) 

90 ) 

91 if not model_names: 

92 return {} 

93 use_user_colors = all(mname in colorbar for mname in model_names) 

94 if use_user_colors: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

95 return {mname: colorbar[mname] for mname in model_names} 

96 

97 # Update to always plot observations as first item with dimgray color 

98 if any("OBS" in name.upper() for name in model_names): 

99 colors = list(DEFAULT_DISCRETE_COLORS).copy() 

100 colors.insert(0, mcolors.to_rgb("dimgray")) 

101 ob_name = next(name for name in model_names if "OBS" in name.upper()) 

102 model_names.remove(ob_name) 

103 model_names.insert(0, ob_name) 

104 else: 

105 colors = DEFAULT_DISCRETE_COLORS 

106 

107 color_list = itertools.cycle(colors) 

108 return {mname: color for mname, color in zip(model_names, color_list, strict=False)} 

109 

110 

111def colorbar_map_levels(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None): 

112 """Get an appropriate colorbar for the given cube. 

113 

114 For the given variable the appropriate colorbar is looked up from a 

115 combination of the built-in CSET colorbar definitions, and any user supplied 

116 definitions. As well as varying on variables, these definitions may also 

117 exist for specific pressure levels to account for variables with 

118 significantly different ranges at different heights. The colorbars also exist 

119 for masks and mask differences for considering variable presence diagnostics. 

120 Specific variable ranges can be separately set in user-supplied definition 

121 for x- or y-axis limits, or indicate where automated range preferred. 

122 

123 Parameters 

124 ---------- 

125 cube: Cube 

126 Cube of variable for which the colorbar information is desired. 

127 axis: "x", "y", optional 

128 Select the levels for just this axis of a line plot. The min and max 

129 can be set by xmin/xmax or ymin/ymax respectively. For variables where 

130 setting a universal range is not desirable (e.g. temperature), users 

131 can set ymin/ymax values to "auto" in the colorbar definitions file. 

132 Where no additional xmin/xmax or ymin/ymax values are provided, the 

133 axis bounds default to use the vmin/vmax values provided. 

134 

135 Returns 

136 ------- 

137 cmap: 

138 Matplotlib colormap. 

139 levels: 

140 List of levels to use for plotting. For continuous plots the min and max 

141 should be taken as the range. 

142 norm: 

143 BoundaryNorm information. 

144 """ 

145 # Grab the colorbar file from the recipe global metadata. 

146 user_colorbar_file = get_recipe_metadata().get("style_file_path", None) 

147 colorbar = load_colorbar_map(user_colorbar_file) 

148 cmap = None 

149 

150 try: 

151 # We assume that pressure is a scalar coordinate here. 

152 pressure_level_raw = cube.coord("pressure").points[0] 

153 # Ensure pressure_level is a string, as it is used as a JSON key. 

154 pressure_level = str(int(pressure_level_raw)) 

155 except iris.exceptions.CoordinateNotFoundError: 

156 pressure_level = None 

157 

158 # First try long name, then standard name, then var name. This order is used 

159 # as long name is the one we correct between models, so it most likely to be 

160 # consistent. 

161 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name])) 

162 # Treat observation-labelled var names consistently with model var names. 

163 varnames = [varname.replace("observed_", "") for varname in varnames] 

164 for varname in varnames: 

165 # Get the colormap for this variable. 

166 try: 

167 var_colorbar = colorbar[varname] 

168 cmap = plt.get_cmap(colorbar[varname]["cmap"], 51) 

169 varname_key = varname 

170 break 

171 except KeyError: 

172 logger.debug("Cube name %s has no colorbar definition.", varname) 

173 

174 # Get colormap if it is a mask. 

175 if any("mask_for_" in name for name in varnames): 

176 cmap, levels, norm = custom_colormap_mask(cube, axis=axis) 

177 return cmap, levels, norm 

178 # If winds on Beaufort Scale use custom colorbar and levels 

179 if any("Beaufort_Scale" in name for name in varnames): 

180 cmap, levels, norm = custom_beaufort_scale(cube, axis=axis) 

181 return cmap, levels, norm 

182 # If probability is plotted use custom colorbar and levels 

183 if any("probability_of_" in name for name in varnames): 

184 cmap, levels, norm = custom_colormap_probability(cube, axis=axis) 

185 return cmap, levels, norm 

186 # If aviation colour state use custom colorbar and levels 

187 if any("aviation_colour_state" in name for name in varnames): 

188 cmap, levels, norm = custom_colormap_aviation_colour_state(cube) 

189 return cmap, levels, norm 

190 # If verification scores use custom colorbar 

191 if any("RMSE_" in name for name in varnames): 

192 cmap, levels, norm = custom_colormap_scores(cube) 

193 return cmap, levels, norm 

194 # If feature tracking use custom colorbar and levels 

195 if any("feature_" in name for name in varnames): 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true

196 cmap, levels, norm = custom_colormap_feature_tracking(cube) 

197 return cmap, levels, norm 

198 

199 # If no valid colormap has been defined, use defaults and return. 

200 if not cmap: 

201 logger.warning("No colorbar definition exists for %s.", cube.name()) 

202 cmap, levels, norm = mpl.colormaps["viridis"], None, None 

203 return cmap, levels, norm 

204 

205 # Test if pressure-level specific settings are provided for cube. 

206 if pressure_level: 

207 try: 

208 var_colorbar = colorbar[varname_key]["pressure_levels"][pressure_level] 

209 except KeyError: 

210 logger.debug( 

211 "%s has no colorbar definition for pressure level %s.", 

212 varname, 

213 pressure_level, 

214 ) 

215 

216 # Check for availability of x-axis or y-axis user-specific overrides 

217 # for setting level bounds for line plot types and return just levels. 

218 # Line plots do not need a colormap, and just use the data range. 

219 if axis: 

220 if axis == "x": 

221 try: 

222 vmin, vmax = var_colorbar["xmin"], var_colorbar["xmax"] 

223 except KeyError: 

224 vmin, vmax = var_colorbar["min"], var_colorbar["max"] 

225 if axis == "y": 

226 try: 

227 vmin, vmax = var_colorbar["ymin"], var_colorbar["ymax"] 

228 except KeyError: 

229 vmin, vmax = var_colorbar["min"], var_colorbar["max"] 

230 # Check if user-specified auto-scaling for this variable 

231 if vmin == "auto" or vmax == "auto": 

232 levels = None 

233 else: 

234 levels = [vmin, vmax] 

235 return None, levels, None 

236 # Get and use the colorbar levels for this variable if spatial or histogram. 

237 else: 

238 try: 

239 levels = var_colorbar["levels"] 

240 # Use discrete bins when levels are specified, rather 

241 # than a smooth range. 

242 norm = mpl.colors.BoundaryNorm(levels, ncolors=cmap.N) 

243 logger.debug("Using levels for %s colorbar.", varname) 

244 logger.info("Using levels: %s", levels) 

245 except KeyError: 

246 # Get the range for this variable. 

247 vmin, vmax = var_colorbar["min"], var_colorbar["max"] 

248 logger.debug("Using min and max for %s colorbar.", varname) 

249 # Calculate levels from range. 

250 if vmin == "auto" or vmax == "auto": 

251 levels = None 

252 else: 

253 levels = np.linspace(vmin, vmax, 101) 

254 norm = None 

255 

256 # Overwrite cmap, levels and norm for specific variables that 

257 # require custom colorbar_map as these can not be defined in the 

258 # JSON file. 

259 cmap, levels, norm = custom_colormap_precipitation(cube, cmap, levels, norm) 

260 cmap, levels, norm = custom_colourmap_nimrod_weights(cube, cmap, levels, norm) 

261 cmap, levels, norm = custom_colormap_visibility_in_air(cube, cmap, levels, norm) 

262 cmap, levels, norm = custom_colormap_celsius(cube, cmap, levels, norm) 

263 return cmap, levels, norm 

264 

265 

266def custom_colormap_mask(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None): 

267 """Get colormap for mask. 

268 

269 If "mask_for_" appears anywhere in the name of a cube this function will be called 

270 regardless of the name of the variable to ensure a consistent plot. 

271 

272 Parameters 

273 ---------- 

274 cube: Cube 

275 Cube of variable for which the colorbar information is desired. 

276 axis: "x", "y", optional 

277 Select the levels for just this axis of a line plot. The min and max 

278 can be set by xmin/xmax or ymin/ymax respectively. For variables where 

279 setting a universal range is not desirable (e.g. temperature), users 

280 can set ymin/ymax values to "auto" in the colorbar definitions file. 

281 Where no additional xmin/xmax or ymin/ymax values are provided, the 

282 axis bounds default to use the vmin/vmax values provided. 

283 

284 Returns 

285 ------- 

286 cmap: 

287 Matplotlib colormap. 

288 levels: 

289 List of levels to use for plotting. For continuous plots the min and max 

290 should be taken as the range. 

291 norm: 

292 BoundaryNorm information. 

293 """ 

294 if "difference" not in cube.long_name: 

295 if axis: 

296 levels = [0, 1] 

297 # Complete settings based on levels. 

298 return None, levels, None 

299 else: 

300 # Define the levels and colors. 

301 levels = [0, 1, 2] 

302 colors = ["white", "dodgerblue"] 

303 # Create a custom color map. 

304 cmap = mcolors.ListedColormap(colors) 

305 # Normalize the levels. 

306 norm = mcolors.BoundaryNorm(levels, cmap.N) 

307 logger.debug("Colormap for %s.", cube.long_name) 

308 return cmap, levels, norm 

309 else: 

310 if axis: 

311 levels = [-1, 1] 

312 return None, levels, None 

313 else: 

314 # Search for if mask difference, set to +/- 0.5 as values plotted < 

315 # not <=. 

316 levels = [-2, -0.5, 0.5, 2] 

317 colors = ["goldenrod", "white", "teal"] 

318 cmap = mcolors.ListedColormap(colors) 

319 norm = mcolors.BoundaryNorm(levels, cmap.N) 

320 logger.debug("Colormap for %s.", cube.long_name) 

321 return cmap, levels, norm 

322 

323 

324def custom_beaufort_scale(cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None): 

325 """Get a custom colorbar for a cube in the Beaufort Scale. 

326 

327 Specific variable ranges can be separately set in user-supplied definition 

328 for x- or y-axis limits, or indicate where automated range preferred. 

329 

330 Parameters 

331 ---------- 

332 cube: Cube 

333 Cube of variable with Beaufort Scale in name. 

334 axis: "x", "y", optional 

335 Select the levels for just this axis of a line plot. The min and max 

336 can be set by xmin/xmax or ymin/ymax respectively. For variables where 

337 setting a universal range is not desirable (e.g. temperature), users 

338 can set ymin/ymax values to "auto" in the colorbar definitions file. 

339 Where no additional xmin/xmax or ymin/ymax values are provided, the 

340 axis bounds default to use the vmin/vmax values provided. 

341 

342 Returns 

343 ------- 

344 cmap: 

345 Matplotlib colormap. 

346 levels: 

347 List of levels to use for plotting. For continuous plots the min and max 

348 should be taken as the range. 

349 norm: 

350 BoundaryNorm information. 

351 """ 

352 if "difference" not in cube.long_name: 

353 if axis: 

354 levels = [0, 12] 

355 return None, levels, None 

356 else: 

357 levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] 

358 colors = [ 

359 "black", 

360 (0, 0, 0.6), 

361 "blue", 

362 "cyan", 

363 "green", 

364 "yellow", 

365 (1, 0.5, 0), 

366 "red", 

367 "pink", 

368 "magenta", 

369 "purple", 

370 "maroon", 

371 "white", 

372 ] 

373 cmap = mcolors.ListedColormap(colors) 

374 norm = mcolors.BoundaryNorm(levels, cmap.N) 

375 logger.info("change colormap for Beaufort Scale colorbar.") 

376 return cmap, levels, norm 

377 else: 

378 if axis: 

379 levels = [-4, 4] 

380 return None, levels, None 

381 else: 

382 levels = [ 

383 -3.5, 

384 -2.5, 

385 -1.5, 

386 -0.5, 

387 0.5, 

388 1.5, 

389 2.5, 

390 3.5, 

391 ] 

392 cmap = plt.get_cmap("bwr", 8) 

393 norm = mcolors.BoundaryNorm(levels, cmap.N) 

394 return cmap, levels, norm 

395 

396 

397def custom_colormap_celsius(cube: iris.cube.Cube, cmap, levels, norm): 

398 """Return altered colormap for temperature with change in units to Celsius. 

399 

400 If "Celsius" appears anywhere in the name of a cube this function will be called. 

401 

402 Parameters 

403 ---------- 

404 cube: Cube 

405 Cube of variable for which the colorbar information is desired. 

406 cmap: Matplotlib colormap. 

407 levels: List 

408 List of levels to use for plotting. For continuous plots the min and max 

409 should be taken as the range. 

410 norm: BoundaryNorm. 

411 

412 Returns 

413 ------- 

414 cmap: Matplotlib colormap. 

415 levels: List 

416 List of levels to use for plotting. For continuous plots the min and max 

417 should be taken as the range. 

418 norm: BoundaryNorm. 

419 """ 

420 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name]) 

421 if any("temperature" in name for name in varnames) and "Celsius" == cube.units: 

422 levels = np.array(levels) 

423 levels -= 273 

424 levels = levels.tolist() 

425 return cmap, levels, norm 

426 

427 

428def custom_colormap_probability( 

429 cube: iris.cube.Cube, axis: Literal["x", "y"] | None = None 

430): 

431 """Get a custom colorbar for a probability cube. 

432 

433 Specific variable ranges can be separately set in user-supplied definition 

434 for x- or y-axis limits, or indicate where automated range preferred. 

435 

436 Parameters 

437 ---------- 

438 cube: Cube 

439 Cube of variable with probability in name. 

440 axis: "x", "y", optional 

441 Select the levels for just this axis of a line plot. The min and max 

442 can be set by xmin/xmax or ymin/ymax respectively. For variables where 

443 setting a universal range is not desirable (e.g. temperature), users 

444 can set ymin/ymax values to "auto" in the colorbar definitions file. 

445 Where no additional xmin/xmax or ymin/ymax values are provided, the 

446 axis bounds default to use the vmin/vmax values provided. 

447 

448 Returns 

449 ------- 

450 cmap: 

451 Matplotlib colormap. 

452 levels: 

453 List of levels to use for plotting. For continuous plots the min and max 

454 should be taken as the range. 

455 norm: 

456 BoundaryNorm information. 

457 """ 

458 if axis: 

459 levels = [0, 1] 

460 return None, levels, None 

461 else: 

462 cmap = mcolors.ListedColormap( 

463 [ 

464 "#FFFFFF", 

465 "#636363", 

466 "#e1dada", 

467 "#B5CAFF", 

468 "#8FB3FF", 

469 "#7F97FF", 

470 "#ABCF63", 

471 "#E8F59E", 

472 "#FFFA14", 

473 "#FFD121", 

474 "#FFA30A", 

475 ] 

476 ) 

477 levels = [0.0, 0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] 

478 norm = mcolors.BoundaryNorm(levels, cmap.N) 

479 return cmap, levels, norm 

480 

481 

482def custom_colormap_precipitation(cube: iris.cube.Cube, cmap, levels, norm): 

483 """Return a custom colormap for the current recipe.""" 

484 varnames_lower = [ 

485 n.lower() for n in (cube.long_name, cube.standard_name, cube.var_name) if n 

486 ] 

487 

488 is_rainfall_var = any( 

489 key in name 

490 for name in varnames_lower 

491 for key in ( 

492 "surface_microphysical", 

493 "rainfall rate composite", 

494 "nimrod5min", 

495 "nimrod_5min", 

496 "rain_accumulation", 

497 "rain accumulation", 

498 ) 

499 ) 

500 

501 if is_rainfall_var: 

502 logger.debug( 

503 "Using custom precipitation colourmap due to varnames: %s", varnames_lower 

504 ) 

505 levels = [0, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64, 128, 256] 

506 colors = [ 

507 "w", 

508 (0, 0, 0.6), 

509 "b", 

510 "c", 

511 "g", 

512 "y", 

513 (1, 0.5, 0), 

514 "r", 

515 "pink", 

516 "m", 

517 "purple", 

518 "maroon", 

519 "gray", 

520 ] 

521 # Create a custom colormap 

522 cmap = mcolors.ListedColormap(colors) 

523 # Normalize the levels 

524 norm = mcolors.BoundaryNorm(levels, cmap.N) 

525 logger.info("Using custom rainfall colourmap.") 

526 return cmap, levels, norm 

527 

528 

529def custom_colourmap_nimrod_weights(cube: iris.cube.Cube, cmap, levels, norm): 

530 """Return a custom colourmap for the current recipe.""" 

531 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name]) 

532 if ( 

533 any("wts" in name for name in varnames) 

534 and "difference" not in cube.long_name 

535 and "mask" not in cube.long_name 

536 ): 

537 # Define the levels and colors. Remember the Nimrod weights vary over 

538 # the range [0,13] and should be integer values. Optimum value is 13. 

539 levels = [ 

540 -0.5, 

541 0.5, 

542 1.5, 

543 2.5, 

544 3.5, 

545 4.5, 

546 5.5, 

547 6.5, 

548 7.5, 

549 8.5, 

550 9.5, 

551 10.5, 

552 11.5, 

553 12.5, 

554 13.5, 

555 ] 

556 norm = mcolors.BoundaryNorm(levels, cmap.N) 

557 colours = [ 

558 "#d10000", 

559 "purple", 

560 "#8f00d6", 

561 "#ff9700", 

562 "pink", 

563 "#ffff00", 

564 "#00007f", 

565 "#6c9ccd", 

566 "#aae8ff", 

567 "#37a648", 

568 "#8edc64", 

569 "#c5ffc5", 

570 "#dcdcdc", 

571 "#ffffff", 

572 ] 

573 # Create a custom colormap. 

574 cmap = mcolors.ListedColormap(colours) 

575 # Normalize the levels. 

576 norm = mcolors.BoundaryNorm(levels, cmap.N) 

577 logger.info("Change colormap for Nimrod weights colorbar.") 

578 return cmap, levels, norm 

579 

580 

581def custom_colormap_aviation_colour_state(cube: iris.cube.Cube): 

582 """Return custom colormap for aviation colour state. 

583 

584 If "aviation_colour_state" appears anywhere in the name of a cube 

585 this function will be called. 

586 

587 Parameters 

588 ---------- 

589 cube: Cube 

590 Cube of variable for which the colorbar information is desired. 

591 

592 Returns 

593 ------- 

594 cmap: Matplotlib colormap. 

595 levels: List 

596 List of levels to use for plotting. For continuous plots the min and max 

597 should be taken as the range. 

598 norm: BoundaryNorm. 

599 """ 

600 levels = [-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5] 

601 colors = [ 

602 "#87ceeb", 

603 "#ffffff", 

604 "#8ced69", 

605 "#ffff00", 

606 "#ffd700", 

607 "#ffa500", 

608 "#fe3620", 

609 ] 

610 # Create a custom colormap 

611 cmap = mcolors.ListedColormap(colors) 

612 # Normalise the levels 

613 norm = mcolors.BoundaryNorm(levels, cmap.N) 

614 return cmap, levels, norm 

615 

616 

617def custom_colormap_visibility_in_air(cube: iris.cube.Cube, cmap, levels, norm): 

618 """Return a custom colormap for the current recipe.""" 

619 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name]) 

620 if ( 

621 any("visibility_in_air" in name for name in varnames) 

622 and "difference" not in cube.long_name 

623 and "mask" not in cube.long_name 

624 ): 

625 # Define the levels and colors (in km) 

626 levels = [0, 0.05, 0.1, 0.2, 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 50.0, 70.0, 100.0] 

627 norm = mcolors.BoundaryNorm(levels, cmap.N) 

628 colours = [ 

629 "#8f00d6", 

630 "#d10000", 

631 "#ff9700", 

632 "#ffff00", 

633 "#00007f", 

634 "#6c9ccd", 

635 "#aae8ff", 

636 "#37a648", 

637 "#8edc64", 

638 "#c5ffc5", 

639 "#dcdcdc", 

640 "#ffffff", 

641 ] 

642 # Create a custom colormap 

643 cmap = mcolors.ListedColormap(colours) 

644 # Normalize the levels 

645 norm = mcolors.BoundaryNorm(levels, cmap.N) 

646 logger.info("change colormap for visibility_in_air variable colorbar.") 

647 return cmap, levels, norm 

648 

649 

650def custom_colormap_scores(cube: iris.cube.Cube): 

651 """Return altered colormap for statistical metrics. 

652 

653 Parameters 

654 ---------- 

655 cube: Cube 

656 Cube of variable for which the colorbar information is desired. 

657 

658 Returns 

659 ------- 

660 cmap: Matplotlib colormap. 

661 levels: List 

662 List of levels to use for plotting. For continuous plots the min and max 

663 should be taken as the range. 

664 norm: BoundaryNorm. 

665 """ 

666 varnames = filter(None, [cube.long_name, cube.standard_name, cube.var_name]) 

667 cmap, levels, norm = None, None, None 

668 if any("RMSE_" in name for name in varnames): 668 ↛ 670line 668 didn't jump to line 670 because the condition on line 668 was always true

669 cmap = plt.get_cmap("PuRd", 51) 

670 return cmap, levels, norm 

671 

672 

673def custom_colormap_feature_tracking(cube: iris.cube.Cube): 

674 """Return altered colormap for feature tracking. 

675 

676 Parameters 

677 ---------- 

678 cube: Cube 

679 Cube of variable for which the colorbar information is desired. 

680 

681 Returns 

682 ------- 

683 cmap: Matplotlib colormap. 

684 levels: List 

685 List of levels to use for plotting. For continuous plots the min and max 

686 should be taken as the range. 

687 norm: BoundaryNorm. 

688 """ 

689 varnames = list(filter(None, [cube.long_name, cube.standard_name, cube.var_name])) 

690 

691 if any("feature_id" in name for name in varnames): 

692 # Get max lifetime from cube attributes if available, otherwise use max of data 

693 max_id = cube.attributes.get("max_value", np.ma.max(cube.data)) 

694 levels = None 

695 cmap = plt.get_cmap("viridis") 

696 norm = mcolors.Normalize(vmin=1, vmax=max_id, clip=False) 

697 logger.info("change colormap for feature id variable colorbar.") 

698 

699 elif any("feature_lifetime" in name for name in varnames): 

700 # Get max lifetime from cube attributes if available, otherwise use max of data 

701 max_lifetime = cube.attributes.get("max_value", np.ma.max(cube.data)) 

702 levels = None 

703 cmap = plt.get_cmap("YlGnBu") 

704 norm = mcolors.Normalize(vmin=1, vmax=max_lifetime, clip=False) 

705 logger.info("change colormap for feature lifetime variable colorbar.") 

706 

707 elif any("feature_init" in name for name in varnames): 707 ↛ 715line 707 didn't jump to line 715 because the condition on line 707 was always true

708 # Define the levels and colors 

709 levels = np.array([0.5, 1]) 

710 cmap = plt.get_cmap("Blues") 

711 norm = mcolors.BoundaryNorm(levels, cmap.N) 

712 logger.info("change colormap for feature init variable colorbar.") 

713 

714 # Set all non-feature data to white. 

715 cmap = cmap.with_extremes(under="white") 

716 

717 return cmap, levels, norm