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

261 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-06 10:09 +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 if any("CURV_" in name for name in varnames): 210 ↛ 211line 210 didn't jump to line 211 because the condition on line 210 was never true

211 cmap, levels, norm = custom_colormap_curv(cube) 

212 return cmap, levels, norm 

213 

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

215 if not cmap: 

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

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

218 return cmap, levels, norm 

219 

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

221 if pressure_level: 

222 try: 

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

224 except KeyError: 

225 logger.debug( 

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

227 varname, 

228 pressure_level, 

229 ) 

230 

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

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

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

234 if axis: 

235 if axis == "x": 

236 try: 

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

238 except KeyError: 

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

240 if axis == "y": 

241 try: 

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

243 except KeyError: 

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

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

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

247 levels = None 

248 else: 

249 levels = [vmin, vmax] 

250 return None, levels, None 

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

252 else: 

253 try: 

254 levels = var_colorbar["levels"] 

255 # Use discrete bins when levels are specified, rather 

256 # than a smooth range. 

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

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

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

260 except KeyError: 

261 # Get the range for this variable. 

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

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

264 # Calculate levels from range. 

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

266 levels = None 

267 else: 

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

269 norm = None 

270 

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

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

273 # JSON file. 

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

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

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

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

278 return cmap, levels, norm 

279 

280 

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

282 """Get colormap for mask. 

283 

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

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

286 

287 Parameters 

288 ---------- 

289 cube: Cube 

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

291 axis: "x", "y", optional 

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

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

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

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

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

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

298 

299 Returns 

300 ------- 

301 cmap: 

302 Matplotlib colormap. 

303 levels: 

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

305 should be taken as the range. 

306 norm: 

307 BoundaryNorm information. 

308 """ 

309 if "difference" not in cube.long_name: 

310 if axis: 

311 levels = [0, 1] 

312 # Complete settings based on levels. 

313 return None, levels, None 

314 else: 

315 # Define the levels and colors. 

316 levels = [0, 1, 2] 

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

318 # Create a custom color map. 

319 cmap = mcolors.ListedColormap(colors) 

320 # Normalize the levels. 

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

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

323 return cmap, levels, norm 

324 else: 

325 if axis: 

326 levels = [-1, 1] 

327 return None, levels, None 

328 else: 

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

330 # not <=. 

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

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

333 cmap = mcolors.ListedColormap(colors) 

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

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

336 return cmap, levels, norm 

337 

338 

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

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

341 

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

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

344 

345 Parameters 

346 ---------- 

347 cube: Cube 

348 Cube of variable with Beaufort Scale in name. 

349 axis: "x", "y", optional 

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

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

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

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

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

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

356 

357 Returns 

358 ------- 

359 cmap: 

360 Matplotlib colormap. 

361 levels: 

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

363 should be taken as the range. 

364 norm: 

365 BoundaryNorm information. 

366 """ 

367 if "difference" not in cube.long_name: 

368 if axis: 

369 levels = [0, 12] 

370 return None, levels, None 

371 else: 

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

373 colors = [ 

374 "black", 

375 (0, 0, 0.6), 

376 "blue", 

377 "cyan", 

378 "green", 

379 "yellow", 

380 (1, 0.5, 0), 

381 "red", 

382 "pink", 

383 "magenta", 

384 "purple", 

385 "maroon", 

386 "white", 

387 ] 

388 cmap = mcolors.ListedColormap(colors) 

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

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

391 return cmap, levels, norm 

392 else: 

393 if axis: 

394 levels = [-4, 4] 

395 return None, levels, None 

396 else: 

397 levels = [ 

398 -3.5, 

399 -2.5, 

400 -1.5, 

401 -0.5, 

402 0.5, 

403 1.5, 

404 2.5, 

405 3.5, 

406 ] 

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

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

409 return cmap, levels, norm 

410 

411 

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

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

414 

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

416 

417 Parameters 

418 ---------- 

419 cube: Cube 

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

421 cmap: Matplotlib colormap. 

422 levels: List 

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

424 should be taken as the range. 

425 norm: BoundaryNorm. 

426 

427 Returns 

428 ------- 

429 cmap: Matplotlib colormap. 

430 levels: List 

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

432 should be taken as the range. 

433 norm: BoundaryNorm. 

434 """ 

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

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

437 levels = np.array(levels) 

438 levels -= 273 

439 levels = levels.tolist() 

440 return cmap, levels, norm 

441 

442 

443def custom_colormap_probability( 

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

445): 

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

447 

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

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

450 

451 Parameters 

452 ---------- 

453 cube: Cube 

454 Cube of variable with probability in name. 

455 axis: "x", "y", optional 

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

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

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

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

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

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

462 

463 Returns 

464 ------- 

465 cmap: 

466 Matplotlib colormap. 

467 levels: 

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

469 should be taken as the range. 

470 norm: 

471 BoundaryNorm information. 

472 """ 

473 if axis: 

474 levels = [0, 1] 

475 return None, levels, None 

476 else: 

477 cmap = mcolors.ListedColormap( 

478 [ 

479 "#FFFFFF", 

480 "#636363", 

481 "#e1dada", 

482 "#B5CAFF", 

483 "#8FB3FF", 

484 "#7F97FF", 

485 "#ABCF63", 

486 "#E8F59E", 

487 "#FFFA14", 

488 "#FFD121", 

489 "#FFA30A", 

490 ] 

491 ) 

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

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

494 return cmap, levels, norm 

495 

496 

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

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

499 varnames_lower = [ 

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

501 ] 

502 

503 is_rainfall_var = any( 

504 key in name 

505 for name in varnames_lower 

506 for key in ( 

507 "surface_microphysical", 

508 "rainfall rate composite", 

509 "nimrod5min", 

510 "nimrod_5min", 

511 "rain_accumulation", 

512 "rain accumulation", 

513 ) 

514 ) 

515 

516 if is_rainfall_var: 

517 logger.debug( 

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

519 ) 

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

521 colors = [ 

522 "w", 

523 (0, 0, 0.6), 

524 "b", 

525 "c", 

526 "g", 

527 "y", 

528 (1, 0.5, 0), 

529 "r", 

530 "pink", 

531 "m", 

532 "purple", 

533 "maroon", 

534 "gray", 

535 ] 

536 # Create a custom colormap 

537 cmap = mcolors.ListedColormap(colors) 

538 # Normalize the levels 

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

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

541 return cmap, levels, norm 

542 

543 

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

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

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

547 if ( 

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

549 and "difference" not in cube.long_name 

550 and "mask" not in cube.long_name 

551 ): 

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

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

554 levels = [ 

555 -0.5, 

556 0.5, 

557 1.5, 

558 2.5, 

559 3.5, 

560 4.5, 

561 5.5, 

562 6.5, 

563 7.5, 

564 8.5, 

565 9.5, 

566 10.5, 

567 11.5, 

568 12.5, 

569 13.5, 

570 ] 

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

572 colours = [ 

573 "#d10000", 

574 "purple", 

575 "#8f00d6", 

576 "#ff9700", 

577 "pink", 

578 "#ffff00", 

579 "#00007f", 

580 "#6c9ccd", 

581 "#aae8ff", 

582 "#37a648", 

583 "#8edc64", 

584 "#c5ffc5", 

585 "#dcdcdc", 

586 "#ffffff", 

587 ] 

588 # Create a custom colormap. 

589 cmap = mcolors.ListedColormap(colours) 

590 # Normalize the levels. 

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

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

593 return cmap, levels, norm 

594 

595 

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

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

598 

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

600 this function will be called. 

601 

602 Parameters 

603 ---------- 

604 cube: Cube 

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

606 

607 Returns 

608 ------- 

609 cmap: Matplotlib colormap. 

610 levels: List 

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

612 should be taken as the range. 

613 norm: BoundaryNorm. 

614 """ 

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

616 colors = [ 

617 "#87ceeb", 

618 "#ffffff", 

619 "#8ced69", 

620 "#ffff00", 

621 "#ffd700", 

622 "#ffa500", 

623 "#fe3620", 

624 ] 

625 # Create a custom colormap 

626 cmap = mcolors.ListedColormap(colors) 

627 # Normalise the levels 

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

629 return cmap, levels, norm 

630 

631 

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

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

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

635 if ( 

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

637 and "difference" not in cube.long_name 

638 and "mask" not in cube.long_name 

639 ): 

640 # Define the levels and colors (in km) 

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

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

643 colours = [ 

644 "#8f00d6", 

645 "#d10000", 

646 "#ff9700", 

647 "#ffff00", 

648 "#00007f", 

649 "#6c9ccd", 

650 "#aae8ff", 

651 "#37a648", 

652 "#8edc64", 

653 "#c5ffc5", 

654 "#dcdcdc", 

655 "#ffffff", 

656 ] 

657 # Create a custom colormap 

658 cmap = mcolors.ListedColormap(colours) 

659 # Normalize the levels 

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

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

662 return cmap, levels, norm 

663 

664 

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

666 """Return altered colormap for statistical metrics. 

667 

668 Parameters 

669 ---------- 

670 cube: Cube 

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

672 

673 Returns 

674 ------- 

675 cmap: Matplotlib colormap. 

676 levels: List 

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

678 should be taken as the range. 

679 norm: BoundaryNorm. 

680 """ 

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

682 cmap, levels, norm = None, None, None 

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

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

685 return cmap, levels, norm 

686 

687 

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

689 """Return altered colormap for feature tracking. 

690 

691 Parameters 

692 ---------- 

693 cube: Cube 

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

695 

696 Returns 

697 ------- 

698 cmap: Matplotlib colormap. 

699 levels: List 

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

701 should be taken as the range. 

702 norm: BoundaryNorm. 

703 """ 

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

705 

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

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

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

709 levels = None 

710 cmap = plt.get_cmap("viridis") 

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

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

713 

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

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

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

717 levels = None 

718 cmap = plt.get_cmap("YlGnBu") 

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

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

721 

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

723 # Define the levels and colors 

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

725 cmap = plt.get_cmap("Blues") 

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

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

728 

729 # Set all non-feature data to white. 

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

731 

732 return cmap, levels, norm 

733 

734 

735def custom_colormap_curv(cube: iris.cube.Cube): 

736 """Return custom colourmap for curv. 

737 

738 If "CURV_" appears anywhere in the name of a cube 

739 this function will be called. 

740 

741 Parameters 

742 ---------- 

743 cube: Cube 

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

745 

746 Returns 

747 ------- 

748 cmap: Matplotlib colormap. 

749 levels: List 

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

751 should be taken as the range. 

752 norm: BoundaryNorm. 

753 """ 

754 if "16" in cube.long_name: 

755 levels = [-17, -15, -13, -11, -9, -7, -5, -3, -1, 1, 3, 5, 7, 9, 11, 13, 15, 17] 

756 colors = [ 

757 "#01153e", 

758 "#030764", 

759 "#00008b", 

760 "#0000ff", 

761 "#0323df", 

762 "#069af3", 

763 "#00ffff", 

764 "#7fffd4", 

765 "#ffffff", 

766 "#ffd700", 

767 "#fac205", 

768 "#ffa500", 

769 "#f97306", 

770 "#ff4500", 

771 "#ff0000", 

772 "#dc143c", 

773 "#a52a2a", 

774 ] 

775 else: 

776 levels = [-9, -7, -5, -3, -1, 1, 3, 5, 7, 9] 

777 colors = [ 

778 "#01153e", 

779 "#00008b", 

780 "#0323df", 

781 "#00ffff", 

782 "#ffffff", 

783 "#fac205", 

784 "#f97306", 

785 "#ff0000", 

786 "#a52a2a", 

787 ] 

788 # Create a custom colormap 

789 cmap = mcolors.ListedColormap(colors) 

790 # Normalise the levels 

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

792 return cmap, levels, norm