Coverage for colour/colorimetry/generation.py: 94%

79 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2Spectral Generation 

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

4 

5Define objects for generating spectral distributions and multi-spectral 

6distributions with the specified characteristics. 

7 

8- :func:`colour.sd_constant` 

9- :func:`colour.sd_zeros` 

10- :func:`colour.sd_ones` 

11- :func:`colour.msds_constant` 

12- :func:`colour.msds_zeros` 

13- :func:`colour.msds_ones` 

14- :func:`colour.colorimetry.sd_gaussian_normal` 

15- :func:`colour.colorimetry.sd_gaussian_fwhm` 

16- :attr:`colour.SD_GAUSSIAN_METHODS` 

17- :func:`colour.sd_gaussian` 

18- :func:`colour.colorimetry.sd_single_led_Ohno2005` 

19- :attr:`colour.SD_SINGLE_LED_METHODS` 

20- :func:`colour.sd_single_led` 

21- :func:`colour.colorimetry.sd_multi_leds_Ohno2005` 

22- :attr:`colour.SD_MULTI_LEDS_METHODS` 

23- :func:`colour.sd_multi_leds` 

24 

25References 

26---------- 

27- :cite:`Ohno2005` : Ohno, Yoshi. (2005). Spectral design considerations for 

28 white LED color rendering. Optical Engineering, 44(11), 111302. 

29 doi:10.1117/1.2130694 

30- :cite:`Ohno2008a` : Ohno, Yoshiro, & Davis, W. (2008). NIST CQS simulation 

31 (Version 7.4) [Computer software]. 

32 https://drive.google.com/file/d/1PsuU6QjUJjCX6tQyCud6ul2Tbs8rYWW9/view?\ 

33usp=sharing 

34""" 

35 

36from __future__ import annotations 

37 

38import typing 

39 

40import numpy as np 

41 

42from colour.algebra.interpolation import LinearInterpolator 

43from colour.colorimetry import ( 

44 SPECTRAL_SHAPE_DEFAULT, 

45 MultiSpectralDistributions, 

46 SpectralDistribution, 

47 SpectralShape, 

48) 

49 

50if typing.TYPE_CHECKING: 

51 from colour.hints import ( 

52 Any, 

53 ArrayLike, 

54 Literal, 

55 NDArrayFloat, 

56 Sequence, 

57 ) 

58 

59from colour.utilities import ( 

60 CanonicalMapping, 

61 as_float_array, 

62 full, 

63 ones, 

64 validate_method, 

65) 

66 

67__author__ = "Colour Developers" 

68__copyright__ = "Copyright 2013 Colour Developers" 

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

70__maintainer__ = "Colour Developers" 

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

72__status__ = "Production" 

73 

74__all__ = [ 

75 "sd_constant", 

76 "sd_zeros", 

77 "sd_ones", 

78 "msds_constant", 

79 "msds_zeros", 

80 "msds_ones", 

81 "sd_gaussian_normal", 

82 "sd_gaussian_fwhm", 

83 "SD_GAUSSIAN_METHODS", 

84 "sd_gaussian", 

85 "sd_single_led_Ohno2005", 

86 "SD_SINGLE_LED_METHODS", 

87 "sd_single_led", 

88 "sd_multi_leds_Ohno2005", 

89 "SD_MULTI_LEDS_METHODS", 

90 "sd_multi_leds", 

91] 

92 

93 

94def sd_constant( 

95 k: float, shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, **kwargs: Any 

96) -> SpectralDistribution: 

97 """ 

98 Generate a spectral distribution of the specified spectral shape filled with 

99 constant :math:`k` values. 

100 

101 Parameters 

102 ---------- 

103 k 

104 Constant :math:`k` to fill the spectral distribution with. 

105 shape 

106 Spectral shape used to create the spectral distribution. 

107 

108 Other Parameters 

109 ---------------- 

110 kwargs 

111 {:class:`colour.SpectralDistribution`}, 

112 See the documentation of the previously listed class. 

113 

114 Returns 

115 ------- 

116 :class:`colour.SpectralDistribution` 

117 Constant :math:`k` filled spectral distribution. 

118 

119 Notes 

120 ----- 

121 - By default, the spectral distribution will use the shape specified by 

122 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

123 - The interpolator is set to :class:`colour.LinearInterpolator` class. 

124 

125 Examples 

126 -------- 

127 >>> sd = sd_constant(100) 

128 >>> sd.shape 

129 SpectralShape(360.0, 780.0, 1.0) 

130 >>> sd[400] 

131 100.0 

132 """ 

133 

134 settings = {"name": f"{k} Constant", "interpolator": LinearInterpolator} 

135 settings.update(kwargs) 

136 

137 values = full(len(shape.wavelengths), k) 

138 

139 return SpectralDistribution(values, shape.wavelengths, **settings) 

140 

141 

142def sd_zeros( 

143 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, **kwargs: Any 

144) -> SpectralDistribution: 

145 """ 

146 Generate a spectral distribution of the specified spectral shape filled with 

147 zeros. 

148 

149 Parameters 

150 ---------- 

151 shape 

152 Spectral shape used to create the spectral distribution. 

153 

154 Other Parameters 

155 ---------------- 

156 kwargs 

157 {:func:`colour.sd_constant`}, 

158 See the documentation of the previously listed definition. 

159 

160 Returns 

161 ------- 

162 :class:`colour.SpectralDistribution` 

163 Zeros-filled spectral distribution. 

164 

165 Notes 

166 ----- 

167 - By default, the spectral distribution will use the shape specified by 

168 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

169 - The interpolator is set to :class:`colour.LinearInterpolator` class. 

170 

171 Examples 

172 -------- 

173 >>> sd = sd_zeros() 

174 >>> sd.shape 

175 SpectralShape(360.0, 780.0, 1.0) 

176 >>> sd[400] 

177 0.0 

178 """ 

179 

180 return sd_constant(0, shape, **kwargs) 

181 

182 

183def sd_ones( 

184 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, **kwargs: Any 

185) -> SpectralDistribution: 

186 """ 

187 Generate a spectral distribution of the specified spectral shape filled 

188 with ones. 

189 

190 Parameters 

191 ---------- 

192 shape 

193 Spectral shape used to create the spectral distribution. 

194 

195 Other Parameters 

196 ---------------- 

197 kwargs 

198 {:func:`colour.sd_constant`}, 

199 See the documentation of the previously listed definition. 

200 

201 Returns 

202 ------- 

203 :class:`colour.SpectralDistribution` 

204 Ones-filled spectral distribution. 

205 

206 Notes 

207 ----- 

208 - By default, the spectral distribution will use the shape specified by 

209 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

210 - The interpolator is set to :class:`colour.LinearInterpolator` class. 

211 

212 Examples 

213 -------- 

214 >>> sd = sd_ones() 

215 >>> sd.shape 

216 SpectralShape(360.0, 780.0, 1.0) 

217 >>> sd[400] 

218 1.0 

219 """ 

220 

221 return sd_constant(1, shape, **kwargs) 

222 

223 

224def msds_constant( 

225 k: float, 

226 labels: Sequence, 

227 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

228 **kwargs: Any, 

229) -> MultiSpectralDistributions: 

230 """ 

231 Generate multi-spectral distributions with the specified labels and spectral 

232 shape filled with constant :math:`k` values. 

233 

234 Parameters 

235 ---------- 

236 k 

237 Constant :math:`k` to fill the multi-spectral distributions with. 

238 labels 

239 Names to use for the :class:`colour.SpectralDistribution` class 

240 instances. 

241 shape 

242 Spectral shape used to create the multi-spectral distributions. 

243 

244 Other Parameters 

245 ---------------- 

246 kwargs 

247 {:class:`colour.MultiSpectralDistributions`}, 

248 See the documentation of the previously listed class. 

249 

250 Returns 

251 ------- 

252 :class:`colour.MultiSpectralDistributions` 

253 Constant :math:`k` filled multi-spectral distributions. 

254 

255 Notes 

256 ----- 

257 - By default, the multi-spectral distributions will use the shape 

258 specified by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

259 - The interpolator is set to :class:`colour.LinearInterpolator` class. 

260 

261 Examples 

262 -------- 

263 >>> msds = msds_constant(100, labels=["a", "b", "c"]) 

264 >>> msds.shape 

265 SpectralShape(360.0, 780.0, 1.0) 

266 >>> msds[400] 

267 array([ 100., 100., 100.]) 

268 >>> msds.labels # doctest: +SKIP 

269 ['a', 'b', 'c'] 

270 """ 

271 

272 settings = {"name": f"{k} Constant", "interpolator": LinearInterpolator} 

273 settings.update(kwargs) 

274 

275 wavelengths = shape.wavelengths 

276 values = full((len(wavelengths), len(labels)), k) 

277 

278 return MultiSpectralDistributions(values, wavelengths, labels=labels, **settings) 

279 

280 

281def msds_zeros( 

282 labels: Sequence, 

283 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

284 **kwargs: Any, 

285) -> MultiSpectralDistributions: 

286 """ 

287 Generate multi-spectral distributions with the specified labels and spectral 

288 shape filled with zeros. 

289 

290 Parameters 

291 ---------- 

292 labels 

293 Names to use for the :class:`colour.SpectralDistribution` class 

294 instances. 

295 shape 

296 Spectral shape used to create the multi-spectral distributions. 

297 

298 Other Parameters 

299 ---------------- 

300 kwargs 

301 {:func:`colour.msds_constant`}, 

302 See the documentation of the previously listed definition. 

303 

304 Returns 

305 ------- 

306 :class:`colour.MultiSpectralDistributions` 

307 Zero-filled multi-spectral distributions. 

308 

309 Notes 

310 ----- 

311 - By default, the multi-spectral distributions will use the shape 

312 specified by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

313 - The interpolator is set to :class:`colour.LinearInterpolator` class. 

314 

315 Examples 

316 -------- 

317 >>> msds = msds_zeros(labels=["a", "b", "c"]) 

318 >>> msds.shape 

319 SpectralShape(360.0, 780.0, 1.0) 

320 >>> msds[400] 

321 array([ 0., 0., 0.]) 

322 >>> msds.labels # doctest: +SKIP 

323 ['a', 'b', 'c'] 

324 """ 

325 

326 return msds_constant(0, labels, shape, **kwargs) 

327 

328 

329def msds_ones( 

330 labels: Sequence, 

331 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

332 **kwargs: Any, 

333) -> MultiSpectralDistributions: 

334 """ 

335 Generate multi-spectral distributions with the specified labels and 

336 spectral shape filled with ones. 

337 

338 Parameters 

339 ---------- 

340 labels 

341 Names to use for the :class:`colour.SpectralDistribution` class 

342 instances. 

343 shape 

344 Spectral shape used to create the multi-spectral distributions. 

345 

346 Other Parameters 

347 ---------------- 

348 kwargs 

349 {:func:`colour.msds_constant`}, 

350 See the documentation of the previously listed definition. 

351 

352 Returns 

353 ------- 

354 :class:`colour.MultiSpectralDistributions` 

355 Ones-filled multi-spectral distributions. 

356 

357 Notes 

358 ----- 

359 - By default, the multi-spectral distributions will use the shape 

360 specified by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

361 - The interpolator is set to :class:`colour.LinearInterpolator` 

362 class. 

363 

364 Examples 

365 -------- 

366 >>> msds = msds_ones(labels=["a", "b", "c"]) 

367 >>> msds.shape 

368 SpectralShape(360.0, 780.0, 1.0) 

369 >>> msds[400] 

370 array([ 1., 1., 1.]) 

371 >>> msds.labels # doctest: +SKIP 

372 ['a', 'b', 'c'] 

373 """ 

374 

375 return msds_constant(1, labels, shape, **kwargs) 

376 

377 

378def sd_gaussian_normal( 

379 mu: float, 

380 sigma: float, 

381 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

382 **kwargs: Any, 

383) -> SpectralDistribution: 

384 r""" 

385 Generate a Gaussian spectral distribution of the specified spectral shape at 

386 specified mean wavelength :math:`\mu` and standard deviation 

387 :math:`\sigma`. 

388 

389 Parameters 

390 ---------- 

391 mu 

392 Mean wavelength :math:`\mu` at which the Gaussian spectral 

393 distribution will peak. 

394 sigma 

395 Standard deviation :math:`\sigma` of the Gaussian spectral 

396 distribution. 

397 shape 

398 Spectral shape used to create the spectral distribution. 

399 

400 Other Parameters 

401 ---------------- 

402 kwargs 

403 {:class:`colour.SpectralDistribution`}, 

404 See the documentation of the previously listed class. 

405 

406 Returns 

407 ------- 

408 :class:`colour.SpectralDistribution` 

409 Gaussian spectral distribution. 

410 

411 Notes 

412 ----- 

413 - By default, the spectral distribution will use the shape specified by 

414 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

415 

416 Examples 

417 -------- 

418 >>> sd = sd_gaussian_normal(555, 25) 

419 >>> sd.shape 

420 SpectralShape(360.0, 780.0, 1.0) 

421 >>> sd[555] # doctest: +SKIP 

422 1.0 

423 >>> sd[530] # doctest: +ELLIPSIS 

424 0.6065306... 

425 """ 

426 

427 settings = {"name": f"{mu}nm - {sigma} Sigma - Gaussian"} 

428 settings.update(kwargs) 

429 

430 values = np.exp(-((shape.wavelengths - mu) ** 2) / (2 * sigma**2)) 

431 

432 return SpectralDistribution(values, shape.wavelengths, **settings) 

433 

434 

435def sd_gaussian_fwhm( 

436 peak_wavelength: float, 

437 fwhm: float, 

438 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

439 **kwargs: Any, 

440) -> SpectralDistribution: 

441 """ 

442 Generate a Gaussian spectral distribution of the specified spectral shape at 

443 specified peak wavelength and full width at half maximum (FWHM). 

444 

445 Parameters 

446 ---------- 

447 peak_wavelength 

448 Wavelength at which the Gaussian spectral distribution peaks. 

449 fwhm 

450 Full width at half maximum, i.e., width of the Gaussian spectral 

451 distribution measured between those points on the *y* axis which are 

452 half the maximum amplitude. 

453 shape 

454 Spectral shape used to create the spectral distribution. 

455 

456 Other Parameters 

457 ---------------- 

458 kwargs 

459 {:class:`colour.SpectralDistribution`}, 

460 See the documentation of the previously listed class. 

461 

462 Returns 

463 ------- 

464 :class:`colour.SpectralDistribution` 

465 Gaussian spectral distribution. 

466 

467 Notes 

468 ----- 

469 - By default, the spectral distribution will use the shape specified by 

470 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

471 

472 Examples 

473 -------- 

474 >>> sd = sd_gaussian_fwhm(555, 25) 

475 >>> sd.shape 

476 SpectralShape(360.0, 780.0, 1.0) 

477 >>> sd[555] # doctest: +SKIP 

478 1.0 

479 >>> sd[530] # doctest: +ELLIPSIS 

480 0.062... 

481 """ 

482 

483 settings = {"name": f"{peak_wavelength}nm - {fwhm} FWHM - Gaussian"} 

484 settings.update(kwargs) 

485 

486 mu, sigma = peak_wavelength, fwhm / (2 * np.sqrt(2 * np.log(2))) 

487 values = np.exp(-((shape.wavelengths - mu) ** 2) / (2 * sigma**2)) 

488 

489 return SpectralDistribution(values, shape.wavelengths, **settings) 

490 

491 

492SD_GAUSSIAN_METHODS: CanonicalMapping = CanonicalMapping( 

493 {"Normal": sd_gaussian_normal, "FWHM": sd_gaussian_fwhm} 

494) 

495SD_GAUSSIAN_METHODS.__doc__ = """ 

496Supported gaussian spectral distribution generation methods. 

497""" 

498 

499 

500def sd_gaussian( 

501 mu_peak_wavelength: float, 

502 sigma_fwhm: float, 

503 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

504 method: Literal["Normal", "FWHM"] | str = "Normal", 

505 **kwargs: Any, 

506) -> SpectralDistribution: 

507 """ 

508 Generate a Gaussian spectral distribution with the specified spectral 

509 shape using the specified method. 

510 

511 Parameters 

512 ---------- 

513 mu_peak_wavelength 

514 Mean wavelength :math:`\\mu` at which the Gaussian spectral 

515 distribution will peak. 

516 sigma_fwhm 

517 Standard deviation :math:`\\sigma` of the Gaussian spectral 

518 distribution or full width at half maximum (FWHM), i.e., the width 

519 of the Gaussian spectral distribution measured between those points 

520 on the *y* axis which are half the maximum amplitude. 

521 shape 

522 Spectral shape used to create the spectral distribution. 

523 method 

524 Computation method. 

525 

526 Other Parameters 

527 ---------------- 

528 kwargs 

529 {:func:`colour.colorimetry.sd_gaussian_normal`, 

530 :func:`colour.colorimetry.sd_gaussian_fwhm`}, 

531 See the documentation of the previously listed definitions. 

532 

533 Returns 

534 ------- 

535 :class:`colour.SpectralDistribution` 

536 Gaussian spectral distribution. 

537 

538 Notes 

539 ----- 

540 - By default, the spectral distribution will use the shape specified 

541 by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

542 

543 Examples 

544 -------- 

545 >>> sd = sd_gaussian(555, 25) 

546 >>> sd.shape 

547 SpectralShape(360.0, 780.0, 1.0) 

548 >>> sd[555] # doctest: +SKIP 

549 1.0 

550 >>> sd[530] # doctest: +ELLIPSIS 

551 0.6065306... 

552 >>> sd = sd_gaussian(555, 25, method="FWHM") 

553 >>> sd.shape 

554 SpectralShape(360.0, 780.0, 1.0) 

555 >>> sd[555] # doctest: +SKIP 

556 1.0 

557 >>> sd[530] # doctest: +ELLIPSIS 

558 0.062... 

559 """ 

560 

561 method = validate_method(method, tuple(SD_GAUSSIAN_METHODS)) 

562 

563 return SD_GAUSSIAN_METHODS[method](mu_peak_wavelength, sigma_fwhm, shape, **kwargs) 

564 

565 

566def sd_single_led_Ohno2005( 

567 peak_wavelength: float, 

568 half_spectral_width: float, 

569 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

570 **kwargs: Any, 

571) -> SpectralDistribution: 

572 """ 

573 Generate a single *LED* spectral distribution with the specified spectral 

574 shape at specified peak wavelength and half spectral width 

575 :math:`\\Delta\\lambda_{0.5}` using *Ohno (2005)* method. 

576 

577 Parameters 

578 ---------- 

579 peak_wavelength 

580 Wavelength at which the single *LED* spectral distribution peaks. 

581 half_spectral_width 

582 Half spectral width :math:`\\Delta\\lambda_{0.5}`. 

583 shape 

584 Spectral shape used to create the spectral distribution. 

585 

586 Other Parameters 

587 ---------------- 

588 kwargs 

589 {:class:`colour.SpectralDistribution`}, 

590 See the documentation of the previously listed class. 

591 

592 Returns 

593 ------- 

594 :class:`colour.SpectralDistribution` 

595 Single *LED* spectral distribution. 

596 

597 Notes 

598 ----- 

599 - By default, the spectral distribution will use the shape specified 

600 by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

601 

602 References 

603 ---------- 

604 :cite:`Ohno2005`, :cite:`Ohno2008a` 

605 

606 Examples 

607 -------- 

608 >>> sd = sd_single_led_Ohno2005(555, 25) 

609 >>> sd.shape 

610 SpectralShape(360.0, 780.0, 1.0) 

611 >>> sd[555] # doctest: +ELLIPSIS 

612 1... 

613 """ 

614 

615 settings = { 

616 "name": f"{peak_wavelength}nm - {half_spectral_width} " 

617 f"Half Spectral Width LED - Ohno (2005)" 

618 } 

619 settings.update(kwargs) 

620 

621 values = np.exp( 

622 -(((shape.wavelengths - peak_wavelength) / half_spectral_width) ** 2) 

623 ) 

624 values = (values + 2 * values**5) / 3 

625 

626 return SpectralDistribution(values, shape.wavelengths, **settings) 

627 

628 

629SD_SINGLE_LED_METHODS: CanonicalMapping = CanonicalMapping( 

630 { 

631 "Ohno 2005": sd_single_led_Ohno2005, 

632 } 

633) 

634SD_SINGLE_LED_METHODS.__doc__ = """ 

635Supported single *LED* spectral distribution computation methods. 

636""" 

637 

638 

639def sd_single_led( 

640 peak_wavelength: float, 

641 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

642 method: Literal["Ohno 2005"] | str = "Ohno 2005", 

643 **kwargs: Any, 

644) -> SpectralDistribution: 

645 """ 

646 Generate a single *LED* spectral distribution with the specified spectral 

647 shape at the specified peak wavelength using the specified method. 

648 

649 Parameters 

650 ---------- 

651 peak_wavelength 

652 Wavelength the single *LED* spectral distribution will peak at. 

653 shape 

654 Spectral shape used to create the spectral distribution. 

655 method 

656 Computation method. 

657 

658 Other Parameters 

659 ---------------- 

660 kwargs 

661 {:func:`colour.colorimetry.sd_single_led_Ohno2005`}, 

662 See the documentation of the previously listed definition. 

663 

664 Returns 

665 ------- 

666 :class:`colour.SpectralDistribution` 

667 Single *LED* spectral distribution. 

668 

669 Notes 

670 ----- 

671 - By default, the spectral distribution will use the shape specified by 

672 :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

673 

674 References 

675 ---------- 

676 :cite:`Ohno2005`, :cite:`Ohno2008a` 

677 

678 Examples 

679 -------- 

680 >>> sd = sd_single_led(555, half_spectral_width=25) 

681 >>> sd.shape 

682 SpectralShape(360.0, 780.0, 1.0) 

683 >>> sd[555] # doctest: +ELLIPSIS 

684 1... 

685 """ 

686 

687 method = validate_method(method, tuple(SD_SINGLE_LED_METHODS)) 

688 kwargs["shape"] = shape 

689 

690 return SD_SINGLE_LED_METHODS[method](peak_wavelength, **kwargs) 

691 

692 

693def sd_multi_leds_Ohno2005( 

694 peak_wavelengths: ArrayLike, 

695 half_spectral_widths: ArrayLike, 

696 peak_power_ratios: ArrayLike | None = None, 

697 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

698 **kwargs: Any, 

699) -> SpectralDistribution: 

700 """ 

701 Generate a multi-*LED* spectral distribution with the specified spectral 

702 shape at specified peak wavelengths, half spectral widths 

703 :math:`\\Delta\\lambda_{0.5}`, and peak power ratios according to the 

704 *Ohno (2005)* method. 

705 

706 The multi-*LED* spectral distribution is computed by summing multiple 

707 single *LED* spectral distributions generated with the 

708 :func:`colour.sd_single_led_Ohno2005` function. 

709 

710 Parameters 

711 ---------- 

712 peak_wavelengths 

713 Wavelengths at which the multi-*LED* spectral distribution will 

714 peak, i.e., the peak wavelengths for each constituent single *LED* 

715 spectral distribution. 

716 half_spectral_widths 

717 Half spectral widths :math:`\\Delta\\lambda_{0.5}` for each 

718 constituent single *LED* spectral distribution. 

719 peak_power_ratios 

720 Peak power ratios for each constituent single *LED* spectral 

721 distribution. If not specified, defaults to unity for all *LEDs*. 

722 shape 

723 Spectral shape used to create the spectral distribution. 

724 

725 Other Parameters 

726 ---------------- 

727 kwargs 

728 {:func:`colour.colorimetry.sd_single_led_Ohno2005`}, 

729 See the documentation of the previously listed definition. 

730 

731 Returns 

732 ------- 

733 :class:`colour.SpectralDistribution` 

734 Multi-*LED* spectral distribution. 

735 

736 Notes 

737 ----- 

738 - By default, the spectral distribution will use the shape specified 

739 by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

740 

741 References 

742 ---------- 

743 :cite:`Ohno2005`, :cite:`Ohno2008a` 

744 

745 Examples 

746 -------- 

747 >>> sd = sd_multi_leds_Ohno2005( 

748 ... np.array([457, 530, 615]), 

749 ... np.array([20, 30, 20]), 

750 ... np.array([0.731, 1.000, 1.660]), 

751 ... ) 

752 >>> sd.shape 

753 SpectralShape(360.0, 780.0, 1.0) 

754 >>> sd[500] # doctest: +ELLIPSIS 

755 0.1295132... 

756 """ 

757 

758 peak_wavelengths = as_float_array(peak_wavelengths) 

759 half_spectral_widths = np.resize(half_spectral_widths, peak_wavelengths.shape) 

760 if peak_power_ratios is None: 

761 peak_power_ratios = ones(peak_wavelengths.shape) 

762 else: 

763 peak_power_ratios = np.resize(peak_power_ratios, peak_wavelengths.shape) 

764 

765 sd = sd_zeros(shape) 

766 

767 for peak_wavelength, half_spectral_width, peak_power_ratio in zip( 

768 peak_wavelengths, half_spectral_widths, peak_power_ratios, strict=True 

769 ): 

770 sd += ( 

771 sd_single_led_Ohno2005(peak_wavelength, half_spectral_width, **kwargs) 

772 * peak_power_ratio 

773 ) 

774 

775 def _format_array(a: NDArrayFloat) -> str: 

776 """Format specified array :math:`a`.""" 

777 

778 return ", ".join([str(e) for e in a]) 

779 

780 sd.name = ( 

781 f"{_format_array(peak_wavelengths)}nm - " 

782 f"{_format_array(half_spectral_widths)} FWHM - " 

783 f"{_format_array(peak_power_ratios)} Peak Power Ratios - " 

784 f"LED - Ohno (2005)" 

785 ) 

786 

787 return sd 

788 

789 

790SD_MULTI_LEDS_METHODS: CanonicalMapping = CanonicalMapping( 

791 { 

792 "Ohno 2005": sd_multi_leds_Ohno2005, 

793 } 

794) 

795SD_MULTI_LEDS_METHODS.__doc__ = """ 

796Supported multi-*LED* spectral distribution computation methods. 

797""" 

798 

799 

800def sd_multi_leds( 

801 peak_wavelengths: ArrayLike, 

802 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT, 

803 method: Literal["Ohno 2005"] | str = "Ohno 2005", 

804 **kwargs: Any, 

805) -> SpectralDistribution: 

806 """ 

807 Generate a multi-*LED* spectral distribution with the specified spectral 

808 shape at specified peak wavelengths. 

809 

810 Parameters 

811 ---------- 

812 peak_wavelengths 

813 Wavelengths at which the multi-*LED* spectral distribution will 

814 peak, i.e., the peak wavelengths for each generated single *LED* 

815 spectral distribution. 

816 shape 

817 Spectral shape used to create the spectral distribution. 

818 method 

819 Computation method. 

820 

821 Other Parameters 

822 ---------------- 

823 kwargs 

824 {:func:`colour.colorimetry.sd_multi_leds_Ohno2005`}, 

825 See the documentation of the previously listed definition. 

826 

827 Returns 

828 ------- 

829 :class:`colour.SpectralDistribution` 

830 Multi-*LED* spectral distribution. 

831 

832 Notes 

833 ----- 

834 - By default, the spectral distribution will use the shape specified 

835 by :attr:`colour.SPECTRAL_SHAPE_DEFAULT` attribute. 

836 

837 References 

838 ---------- 

839 :cite:`Ohno2005`, :cite:`Ohno2008a` 

840 

841 Examples 

842 -------- 

843 >>> sd = sd_multi_leds( 

844 ... np.array([457, 530, 615]), 

845 ... half_spectral_widths=np.array([20, 30, 20]), 

846 ... peak_power_ratios=np.array([0.731, 1.000, 1.660]), 

847 ... ) 

848 >>> sd.shape 

849 SpectralShape(360.0, 780.0, 1.0) 

850 >>> sd[500] # doctest: +ELLIPSIS 

851 0.1295132... 

852 """ 

853 

854 method = validate_method(method, tuple(SD_MULTI_LEDS_METHODS)) 

855 kwargs["shape"] = shape 

856 

857 return SD_MULTI_LEDS_METHODS[method](peak_wavelengths, **kwargs)