Coverage for plotting/quality.py: 68%

1"""

2 Colour Quality Plotting

3=======================

4

5 Define the colour quality plotting objects.

6

7- :func:`colour.plotting.plot_single_sd_colour_rendering_index_bars`

8- :func:`colour.plotting.plot_multi_sds_colour_rendering_indexes_bars`

9- :func:`colour.plotting.plot_single_sd_colour_quality_scale_bars`

10- :func:`colour.plotting.plot_multi_sds_colour_quality_scales_bars`

11"""

12

13 from __future__ import annotations

14

15 import typing

16

17 if typing.TYPE_CHECKING:

18 from collections.abc import ValuesView

19

20 from itertools import cycle

21

22 if typing.TYPE_CHECKING:

23 from matplotlib.figure import Figure

24 from matplotlib.axes import Axes

25

26 import numpy as np

27

28 from colour.colorimetry import (

29 MultiSpectralDistributions,

30 SpectralDistribution,

31 sds_and_msds_to_sds,

32)

33 from colour.constants import DTYPE_FLOAT_DEFAULT

34

35 if typing.TYPE_CHECKING:

36 from colour.hints import (

37 Any,

38 Dict,

39 Literal,

40 Sequence,

41 Tuple,

42 )

43

44 from colour.plotting import (

45 CONSTANTS_COLOUR_STYLE,

46 XYZ_to_plotting_colourspace,

47 artist,

48 label_rectangles,

49 override_style,

50 render,

51)

52 from colour.quality import (

53 COLOUR_QUALITY_SCALE_METHODS,

54 ColourRendering_Specification_CQS,

55 ColourRendering_Specification_CRI,

56 colour_quality_scale,

57 colour_rendering_index,

58)

59 from colour.utilities import as_float_array, ones, validate_method

60

61 __author__ = "Colour Developers"

62 __copyright__ = "Copyright 2013 Colour Developers"

63 __license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"

64 __maintainer__ = "Colour Developers"

65 __email__ = "colour-developers@colour-science.org"

66 __status__ = "Production"

67

68 __all__ = [

69 "plot_colour_quality_bars",

70 "plot_single_sd_colour_rendering_index_bars",

71 "plot_multi_sds_colour_rendering_indexes_bars",

72 "plot_single_sd_colour_quality_scale_bars",

73 "plot_multi_sds_colour_quality_scales_bars",

74]

75

76

77@override_style()

78 def plot_colour_quality_bars(

79 specifications: Sequence[

80 ColourRendering_Specification_CQS | ColourRendering_Specification_CRI

81 ],

82 labels: bool = True,

83 hatching: bool | None = None,

84 hatching_repeat: int = 2,

85 **kwargs: Any,

86) -> Tuple[Figure, Axes]:

87 """

88 Plot the colour quality data of the specified illuminants or light sources

89 colour quality specifications.

90

91 Parameters

92 ----------

93 specifications

94 Array of illuminants or light sources colour quality

95 specifications.

96 labels

97 Add labels above bars.

98 hatching

99 Use hatching for the bars.

100 hatching_repeat

101 Hatching pattern repeat.

102

103 Other Parameters

104 ----------------

105 kwargs

106 {:func:`colour.plotting.artist`,

107 :func:`colour.plotting.quality.plot_colour_quality_bars`,

108 :func:`colour.plotting.render`},

109 See the documentation of the previously listed definitions.

110

111 Returns

112 -------

113 :class:`tuple`

114 Current figure and axes.

115

116 Examples

117 --------

118 >>> from colour import SDS_ILLUMINANTS, SDS_LIGHT_SOURCES, SpectralShape

119 >>> illuminant = SDS_ILLUMINANTS["FL2"]

120 >>> light_source = SDS_LIGHT_SOURCES["Kinoton 75P"]

121 >>> light_source = light_source.copy().align(SpectralShape(360, 830, 1))

122 >>> cqs_i = colour_quality_scale(illuminant, additional_data=True)

123 >>> cqs_l = colour_quality_scale(light_source, additional_data=True)

124 >>> plot_colour_quality_bars([cqs_i, cqs_l]) # doctest: +ELLIPSIS

125 (<Figure size ... with 1 Axes>, <...Axes...>)

126

127 .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png

128 :align: center

129 :alt: plot_colour_quality_bars

130 """

131

132 settings: Dict[str, Any] = {"uniform": True}

133 settings.update(kwargs)

134

135 _figure, axes = artist(**settings)

136

137 bar_width = 0.5

138 y_ticks_interval = 10

139 count_s, count_Q_as = len(specifications), 0

140 patterns = cycle(CONSTANTS_COLOUR_STYLE.hatch.patterns)

141 if hatching is None:

142 hatching = count_s != 1

143

144 for i, specification in enumerate(specifications):

145 Q_a, Q_as, colorimetry_data = (

146 specification.Q_a,

147 specification.Q_as,

148 specification.colorimetry_data,

149 )

150

151 count_Q_as = len(Q_as)

152 RGB = [ones(3)] + [

153 np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1)

154 for x in colorimetry_data[0]

155 ]

156

157 x = (

158 as_float_array(

159 i

160 + np.arange(

161 0,

162 (count_Q_as + 1) * (count_s + 1),

163 (count_s + 1),

164 dtype=DTYPE_FLOAT_DEFAULT,

165 )

166 )

167 * bar_width

168 )

169 y = as_float_array(

170 [Q_a] + [s[1].Q_a for s in sorted(Q_as.items(), key=lambda s: s[0])]

171 )

172

173 bars = axes.bar(

174 x,

175 np.abs(y),

176 color=RGB,

177 width=bar_width,

178 edgecolor=CONSTANTS_COLOUR_STYLE.colour.dark,

179 label=specification.name,

180 zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,

181 )

182

183 hatches = (

184 [next(patterns) * hatching_repeat] * (count_Q_as + 1)

185 if hatching

186 else list(np.where(y < 0, next(patterns), None)) # pyright: ignore

187 )

188

189 for j, bar in enumerate(bars.patches):

190 bar.set_hatch(hatches[j])

191

192 if labels:

193 label_rectangles(

194 [f"{y_v:.1f}" for y_v in y],

195 bars,

196 rotation="horizontal" if count_s == 1 else "vertical",

197 offset=(

198 0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000,

199 0.025,

200 ),

201 text_size=-5 / 7 * count_s + 12.5,

202 axes=axes,

203 )

204

205 axes.axhline(

206 y=100,

207 color=CONSTANTS_COLOUR_STYLE.colour.dark,

208 linestyle="--",

209 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,

210 )

211

212 axes.set_xticks(

213 (

214 np.arange(

215 0,

216 (count_Q_as + 1) * (count_s + 1),

217 (count_s + 1),

218 dtype=DTYPE_FLOAT_DEFAULT,

219 )

220 - bar_width

221 )

222 * bar_width

223 + (count_s * bar_width / 2)

224 )

225 axes.set_xticklabels(

226 ["Qa"] + [f"Q{index + 1}" for index in range(0, count_Q_as, 1)]

227 )

228 axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval))

229

230 aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2))

231 bounding_box = (

232 -bar_width,

233 ((count_Q_as + 1) * (count_s + 1)) / 2 - bar_width,

234 0,

235 120,

236 )

237

238 settings = {

239 "axes": axes,

240 "aspect": aspect,

241 "bounding_box": bounding_box,

242 "legend": hatching,

243 "title": "Colour Quality",

244 }

245 settings.update(kwargs)

246

247 return render(**settings)

248

249

250@override_style()

251 def plot_single_sd_colour_rendering_index_bars(

252 sd: SpectralDistribution, **kwargs: Any

253) -> Tuple[Figure, Axes]:

254 """

255 Plot the *Colour Rendering Index* (CRI) of the specified illuminant or

256 light source spectral distribution.

257

258 Parameters

259 ----------

260 sd

261 Illuminant or light source spectral distribution for which to plot

262 the *Colour Rendering Index* (CRI).

263

264 Other Parameters

265 ----------------

266 kwargs

267 {:func:`colour.plotting.artist`,

268 :func:`colour.plotting.quality.plot_colour_quality_bars`,

269 :func:`colour.plotting.render`},

270 See the documentation of the previously listed definitions.

271

272 Returns

273 -------

274 :class:`tuple`

275 Current figure and axes.

276

277 Examples

278 --------

279 >>> from colour import SDS_ILLUMINANTS

280 >>> illuminant = SDS_ILLUMINANTS["FL2"]

281 >>> plot_single_sd_colour_rendering_index_bars(illuminant)

282 ... # doctest: +ELLIPSIS

283 (<Figure size ... with 1 Axes>, <...Axes...>)

284

285 .. image:: ../_static/Plotting_\

286 Plot_Single_SD_Colour_Rendering_Index_Bars.png

287 :align: center

288 :alt: plot_single_sd_colour_rendering_index_bars

289 """

290

291 return plot_multi_sds_colour_rendering_indexes_bars([sd], **kwargs)

292

293

294@override_style()

295 def plot_multi_sds_colour_rendering_indexes_bars(

296 sds: (

297 Sequence[SpectralDistribution | MultiSpectralDistributions]

298 | SpectralDistribution

299 | MultiSpectralDistributions

300 | ValuesView

301 ),

302 **kwargs: Any,

303) -> Tuple[Figure, Axes]:

304 """

305 Plot the *Colour Rendering Index* (CRI) of the specified illuminants or

306 light sources spectral distributions.

307

308 Parameters

309 ----------

310 sds

311 Spectral distributions or multi-spectral distributions to plot.

312 `sds` can be a single :class:`colour.MultiSpectralDistributions`

313 class instance, a list of :class:`colour.MultiSpectralDistributions`

314 class instances or a list of :class:`colour.SpectralDistribution` class

315 instances.

316

317 Other Parameters

318 ----------------

319 kwargs

320 {:func:`colour.plotting.artist`,

321 :func:`colour.plotting.quality.plot_colour_quality_bars`,

322 :func:`colour.plotting.render`},

323 See the documentation of the previously listed definitions.

324

325 Returns

326 -------

327 :class:`tuple`

328 Current figure and axes.

329

330 Examples

331 --------

332 >>> from colour import SDS_ILLUMINANTS, SDS_LIGHT_SOURCES

333 >>> illuminant = SDS_ILLUMINANTS["FL2"]

334 >>> light_source = SDS_LIGHT_SOURCES["Kinoton 75P"]

335 >>> plot_multi_sds_colour_rendering_indexes_bars(

336 ... [illuminant, light_source]

337 ... ) # doctest: +ELLIPSIS

338 (<Figure size ... with 1 Axes>, <...Axes...>)

339

340 .. image:: ../_static/Plotting_\

341 Plot_Multi_SDS_Colour_Rendering_Indexes_Bars.png

342 :align: center

343 :alt: plot_multi_sds_colour_rendering_indexes_bars

344 """

345

346 sds_converted = sds_and_msds_to_sds(sds)

347

348 settings: Dict[str, Any] = dict(kwargs)

349 settings.update({"show": False})

350

351 specifications = [

352 colour_rendering_index(sd, additional_data=True) for sd in sds_converted

353 ]

354

355 # *colour rendering index* colorimetry data tristimulus values are

356 # computed in [0, 100] domain however `plot_colour_quality_bars` expects

357 # [0, 1] domain. As we want to keep `plot_colour_quality_bars` definition

358 # agnostic from the colour quality data, we update the test sd

359 # colorimetry data tristimulus values domain.

360 for specification in specifications:

361 colorimetry_data = specification.colorimetry_data

362 for i in range(len(colorimetry_data[0])):

363 colorimetry_data[0][i].XYZ /= 100

364

365 _figure, axes = plot_colour_quality_bars(specifications, **settings)

366

367 title = (

368 f"Colour Rendering Index - "

369 f"{', '.join([sd.display_name for sd in sds_converted])}"

370 )

371

372 settings = {"axes": axes, "title": title}

373 settings.update(kwargs)

374

375 return render(**settings)

376

377

378@override_style()

379 def plot_single_sd_colour_quality_scale_bars(

380 sd: SpectralDistribution,

381 method: Literal["NIST CQS 7.4", "NIST CQS 9.0"] | str = "NIST CQS 9.0",

382 **kwargs: Any,

383) -> Tuple[Figure, Axes]:

384 """

385 Plot the *Colour Quality Scale* (CQS) of the specified illuminant or

386 light source spectral distribution.

387

388 Parameters

389 ----------

390 sd

391 Illuminant or light source spectral distribution for which to plot

392 the *Colour Quality Scale* (CQS).

393 method

394 *Colour Quality Scale* (CQS) computation method.

395

396 Other Parameters

397 ----------------

398 kwargs

399 {:func:`colour.plotting.artist`,

400 :func:`colour.plotting.quality.plot_colour_quality_bars`,

401 :func:`colour.plotting.render`},

402 See the documentation of the previously listed definitions.

403

404 Returns

405 -------

406 :class:`tuple`

407 Current figure and axes.

408

409 Examples

410 --------

411 >>> from colour import SDS_ILLUMINANTS

412 >>> illuminant = SDS_ILLUMINANTS["FL2"]

413 >>> plot_single_sd_colour_quality_scale_bars(illuminant)

414 ... # doctest: +ELLIPSIS

415 (<Figure size ... with 1 Axes>, <...Axes...>)

416

417 .. image:: ../_static/Plotting_\

418 Plot_Single_SD_Colour_Quality_Scale_Bars.png

419 :align: center

420 :alt: plot_single_sd_colour_quality_scale_bars

421 """

422

423 method = validate_method(method, tuple(COLOUR_QUALITY_SCALE_METHODS))

424

425 return plot_multi_sds_colour_quality_scales_bars([sd], method, **kwargs)

426

427

428@override_style()

429 def plot_multi_sds_colour_quality_scales_bars(

430 sds: (

431 Sequence[SpectralDistribution | MultiSpectralDistributions]

432 | SpectralDistribution

433 | MultiSpectralDistributions

434 | ValuesView

435 ),

436 method: Literal["NIST CQS 7.4", "NIST CQS 9.0"] | str = "NIST CQS 9.0",

437 **kwargs: Any,

438) -> Tuple[Figure, Axes]:

439 """

440 Plot the *Colour Quality Scale* (CQS) of the specified illuminants or light

441 sources spectral distributions.

442

443 Parameters

444 ----------

445 sds

446 Spectral distributions or multi-spectral distributions to plot.

447 `sds` can be a single :class:`colour.MultiSpectralDistributions`

448 class instance, a list of :class:`colour.MultiSpectralDistributions`

449 class instances or a list of :class:`colour.SpectralDistribution` class

450 instances.

451 method

452 *Colour Quality Scale* (CQS) computation method.

453

454 Other Parameters

455 ----------------

456 kwargs

457 {:func:`colour.plotting.artist`,

458 :func:`colour.plotting.quality.plot_colour_quality_bars`,

459 :func:`colour.plotting.render`},

460 See the documentation of the previously listed definitions.

461

462 Returns

463 -------

464 :class:`tuple`

465 Current figure and axes.

466

467 Examples

468 --------

469 >>> from colour import SDS_ILLUMINANTS, SDS_LIGHT_SOURCES

470 >>> illuminant = SDS_ILLUMINANTS["FL2"]

471 >>> light_source = SDS_LIGHT_SOURCES["Kinoton 75P"]

472 >>> plot_multi_sds_colour_quality_scales_bars([illuminant, light_source])

473 ... # doctest: +ELLIPSIS

474 (<Figure size ... with 1 Axes>, <...Axes...>)

475

476 .. image:: ../_static/Plotting_\

477 Plot_Multi_SDS_Colour_Quality_Scales_Bars.png

478 :align: center

479 :alt: plot_multi_sds_colour_quality_scales_bars

480 """

481

482 method = validate_method(method, tuple(COLOUR_QUALITY_SCALE_METHODS))

483

484 sds_converted = sds_and_msds_to_sds(sds)

485

486 settings: Dict[str, Any] = dict(kwargs)

487 settings.update({"show": False})

488

489 specifications = [colour_quality_scale(sd, True, method) for sd in sds_converted]

490

491 _figure, axes = plot_colour_quality_bars(specifications, **settings)

492

493 title = (

494 f"Colour Quality Scale - {', '.join([sd.display_name for sd in sds_converted])}"

495 )

496

497 settings = {"axes": axes, "title": title}

498 settings.update(kwargs)

499

500 return render(**settings)