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

249 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-07 15:12 +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 # Supported analysis names 

98 ANALYSIS_NAMES = {"ERA5", "UM_ANALYSIS"} 

99 

100 is_reference = lambda name: "OBS" in name.upper() or name.upper() in ANALYSIS_NAMES 

101 

102 ref_models = [name for name in model_names if is_reference(name)] 

103 

104 if ref_models: 

105 colors = list(DEFAULT_DISCRETE_COLORS).copy() 

106 

107 for name in reversed(ref_models): 

108 model_names.remove(name) 

109 model_names.insert(0, name) 

110 

111 for name in reversed(ref_models): 

112 if "OBS" in name.upper(): 

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

114 else: # ERA5 or UM_ANALYSIS 

115 colors.insert(0, mcolors.to_rgb("black")) 

116 else: 

117 colors = DEFAULT_DISCRETE_COLORS 

118 

119 color_list = itertools.cycle(colors) 

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

121 

122 

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

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

125 

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

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

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

129 exist for specific pressure levels to account for variables with 

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

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

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

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

134 

135 Parameters 

136 ---------- 

137 cube: Cube 

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

139 axis: "x", "y", optional 

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

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

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

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

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

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

146 

147 Returns 

148 ------- 

149 cmap: 

150 Matplotlib colormap. 

151 levels: 

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

153 should be taken as the range. 

154 norm: 

155 BoundaryNorm information. 

156 """ 

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

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

159 colorbar = load_colorbar_map(user_colorbar_file) 

160 cmap = None 

161 

162 try: 

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

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

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

166 pressure_level = str(int(pressure_level_raw)) 

167 except iris.exceptions.CoordinateNotFoundError: 

168 pressure_level = None 

169 

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

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

172 # consistent. 

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

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

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

176 for varname in varnames: 

177 # Get the colormap for this variable. 

178 try: 

179 var_colorbar = colorbar[varname] 

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

181 varname_key = varname 

182 break 

183 except KeyError: 

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

185 

186 # Get colormap if it is a mask. 

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

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

189 return cmap, levels, norm 

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

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

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

193 return cmap, levels, norm 

194 # If probability is plotted use custom colorbar and levels 

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

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

197 return cmap, levels, norm 

198 # If aviation colour state use custom colorbar and levels 

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

200 cmap, levels, norm = custom_colormap_aviation_colour_state(cube) 

201 return cmap, levels, norm 

202 # If verification scores use custom colorbar 

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

204 cmap, levels, norm = custom_colormap_scores(cube) 

205 return cmap, levels, norm 

206 # If feature tracking use custom colorbar and levels 

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

208 cmap, levels, norm = custom_colormap_feature_tracking(cube) 

209 return cmap, levels, norm 

210 

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

212 if not cmap: 

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

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

215 return cmap, levels, norm 

216 

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

218 if pressure_level: 

219 try: 

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

221 except KeyError: 

222 logger.debug( 

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

224 varname, 

225 pressure_level, 

226 ) 

227 

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

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

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

231 if axis: 

232 if axis == "x": 

233 try: 

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

235 except KeyError: 

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

237 if axis == "y": 

238 try: 

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

240 except KeyError: 

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

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

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

244 levels = None 

245 else: 

246 levels = [vmin, vmax] 

247 return None, levels, None 

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

249 else: 

250 try: 

251 levels = var_colorbar["levels"] 

252 # Use discrete bins when levels are specified, rather 

253 # than a smooth range. 

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

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

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

257 except KeyError: 

258 # Get the range for this variable. 

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

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

261 # Calculate levels from range. 

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

263 levels = None 

264 else: 

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

266 norm = None 

267 

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

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

270 # JSON file. 

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

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

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

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

275 return cmap, levels, norm 

276 

277 

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

279 """Get colormap for mask. 

280 

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

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

283 

284 Parameters 

285 ---------- 

286 cube: Cube 

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

288 axis: "x", "y", optional 

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

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

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

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

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

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

295 

296 Returns 

297 ------- 

298 cmap: 

299 Matplotlib colormap. 

300 levels: 

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

302 should be taken as the range. 

303 norm: 

304 BoundaryNorm information. 

305 """ 

306 if "difference" not in cube.long_name: 

307 if axis: 

308 levels = [0, 1] 

309 # Complete settings based on levels. 

310 return None, levels, None 

311 else: 

312 # Define the levels and colors. 

313 levels = [0, 1, 2] 

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

315 # Create a custom color map. 

316 cmap = mcolors.ListedColormap(colors) 

317 # Normalize the levels. 

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

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

320 return cmap, levels, norm 

321 else: 

322 if axis: 

323 levels = [-1, 1] 

324 return None, levels, None 

325 else: 

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

327 # not <=. 

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

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

330 cmap = mcolors.ListedColormap(colors) 

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

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

333 return cmap, levels, norm 

334 

335 

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

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

338 

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

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

341 

342 Parameters 

343 ---------- 

344 cube: Cube 

345 Cube of variable with Beaufort Scale in name. 

346 axis: "x", "y", optional 

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

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

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

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

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

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

353 

354 Returns 

355 ------- 

356 cmap: 

357 Matplotlib colormap. 

358 levels: 

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

360 should be taken as the range. 

361 norm: 

362 BoundaryNorm information. 

363 """ 

364 if "difference" not in cube.long_name: 

365 if axis: 

366 levels = [0, 12] 

367 return None, levels, None 

368 else: 

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

370 colors = [ 

371 "black", 

372 (0, 0, 0.6), 

373 "blue", 

374 "cyan", 

375 "green", 

376 "yellow", 

377 (1, 0.5, 0), 

378 "red", 

379 "pink", 

380 "magenta", 

381 "purple", 

382 "maroon", 

383 "white", 

384 ] 

385 cmap = mcolors.ListedColormap(colors) 

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

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

388 return cmap, levels, norm 

389 else: 

390 if axis: 

391 levels = [-4, 4] 

392 return None, levels, None 

393 else: 

394 levels = [ 

395 -3.5, 

396 -2.5, 

397 -1.5, 

398 -0.5, 

399 0.5, 

400 1.5, 

401 2.5, 

402 3.5, 

403 ] 

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

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

406 return cmap, levels, norm 

407 

408 

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

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

411 

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

413 

414 Parameters 

415 ---------- 

416 cube: Cube 

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

418 cmap: Matplotlib colormap. 

419 levels: List 

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

421 should be taken as the range. 

422 norm: BoundaryNorm. 

423 

424 Returns 

425 ------- 

426 cmap: Matplotlib colormap. 

427 levels: List 

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

429 should be taken as the range. 

430 norm: BoundaryNorm. 

431 """ 

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

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

434 levels = np.array(levels) 

435 levels -= 273 

436 levels = levels.tolist() 

437 return cmap, levels, norm 

438 

439 

440def custom_colormap_probability( 

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

442): 

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

444 

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

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

447 

448 Parameters 

449 ---------- 

450 cube: Cube 

451 Cube of variable with probability in name. 

452 axis: "x", "y", optional 

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

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

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

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

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

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

459 

460 Returns 

461 ------- 

462 cmap: 

463 Matplotlib colormap. 

464 levels: 

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

466 should be taken as the range. 

467 norm: 

468 BoundaryNorm information. 

469 """ 

470 if axis: 

471 levels = [0, 1] 

472 return None, levels, None 

473 else: 

474 cmap = mcolors.ListedColormap( 

475 [ 

476 "#FFFFFF", 

477 "#636363", 

478 "#e1dada", 

479 "#B5CAFF", 

480 "#8FB3FF", 

481 "#7F97FF", 

482 "#ABCF63", 

483 "#E8F59E", 

484 "#FFFA14", 

485 "#FFD121", 

486 "#FFA30A", 

487 ] 

488 ) 

489 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] 

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

491 return cmap, levels, norm 

492 

493 

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

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

496 varnames_lower = [ 

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

498 ] 

499 

500 is_rainfall_var = any( 

501 key in name 

502 for name in varnames_lower 

503 for key in ( 

504 "surface_microphysical", 

505 "rainfall rate composite", 

506 "nimrod5min", 

507 "nimrod_5min", 

508 "rain_accumulation", 

509 "rain accumulation", 

510 ) 

511 ) 

512 

513 if is_rainfall_var: 

514 logger.debug( 

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

516 ) 

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

518 colors = [ 

519 "w", 

520 (0, 0, 0.6), 

521 "b", 

522 "c", 

523 "g", 

524 "y", 

525 (1, 0.5, 0), 

526 "r", 

527 "pink", 

528 "m", 

529 "purple", 

530 "maroon", 

531 "gray", 

532 ] 

533 # Create a custom colormap 

534 cmap = mcolors.ListedColormap(colors) 

535 # Normalize the levels 

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

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

538 return cmap, levels, norm 

539 

540 

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

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

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

544 if ( 

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

546 and "difference" not in cube.long_name 

547 and "mask" not in cube.long_name 

548 ): 

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

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

551 levels = [ 

552 -0.5, 

553 0.5, 

554 1.5, 

555 2.5, 

556 3.5, 

557 4.5, 

558 5.5, 

559 6.5, 

560 7.5, 

561 8.5, 

562 9.5, 

563 10.5, 

564 11.5, 

565 12.5, 

566 13.5, 

567 ] 

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

569 colours = [ 

570 "#d10000", 

571 "purple", 

572 "#8f00d6", 

573 "#ff9700", 

574 "pink", 

575 "#ffff00", 

576 "#00007f", 

577 "#6c9ccd", 

578 "#aae8ff", 

579 "#37a648", 

580 "#8edc64", 

581 "#c5ffc5", 

582 "#dcdcdc", 

583 "#ffffff", 

584 ] 

585 # Create a custom colormap. 

586 cmap = mcolors.ListedColormap(colours) 

587 # Normalize the levels. 

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

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

590 return cmap, levels, norm 

591 

592 

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

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

595 

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

597 this function will be called. 

598 

599 Parameters 

600 ---------- 

601 cube: Cube 

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

603 

604 Returns 

605 ------- 

606 cmap: Matplotlib colormap. 

607 levels: List 

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

609 should be taken as the range. 

610 norm: BoundaryNorm. 

611 """ 

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

613 colors = [ 

614 "#87ceeb", 

615 "#ffffff", 

616 "#8ced69", 

617 "#ffff00", 

618 "#ffd700", 

619 "#ffa500", 

620 "#fe3620", 

621 ] 

622 # Create a custom colormap 

623 cmap = mcolors.ListedColormap(colors) 

624 # Normalise the levels 

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

626 return cmap, levels, norm 

627 

628 

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

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

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

632 if ( 

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

634 and "difference" not in cube.long_name 

635 and "mask" not in cube.long_name 

636 ): 

637 # Define the levels and colors (in km) 

638 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] 

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

640 colours = [ 

641 "#8f00d6", 

642 "#d10000", 

643 "#ff9700", 

644 "#ffff00", 

645 "#00007f", 

646 "#6c9ccd", 

647 "#aae8ff", 

648 "#37a648", 

649 "#8edc64", 

650 "#c5ffc5", 

651 "#dcdcdc", 

652 "#ffffff", 

653 ] 

654 # Create a custom colormap 

655 cmap = mcolors.ListedColormap(colours) 

656 # Normalize the levels 

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

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

659 return cmap, levels, norm 

660 

661 

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

663 """Return altered colormap for statistical metrics. 

664 

665 Parameters 

666 ---------- 

667 cube: Cube 

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

669 

670 Returns 

671 ------- 

672 cmap: Matplotlib colormap. 

673 levels: List 

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

675 should be taken as the range. 

676 norm: BoundaryNorm. 

677 """ 

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

679 cmap, levels, norm = None, None, None 

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

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

682 return cmap, levels, norm 

683 

684 

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

686 """Return altered colormap for feature tracking. 

687 

688 Parameters 

689 ---------- 

690 cube: Cube 

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

692 

693 Returns 

694 ------- 

695 cmap: Matplotlib colormap. 

696 levels: List 

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

698 should be taken as the range. 

699 norm: BoundaryNorm. 

700 """ 

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

702 

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

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

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

706 levels = None 

707 cmap = plt.get_cmap("viridis") 

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

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

710 

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

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

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

714 levels = None 

715 cmap = plt.get_cmap("YlGnBu") 

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

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

718 

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

720 # Define the levels and colors 

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

722 cmap = plt.get_cmap("Blues") 

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

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

725 

726 # Set all non-feature data to white. 

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

728 

729 return cmap, levels, norm