Coverage for colorimetry/blackbody.py: 45%

1"""

2 Blackbody - Planckian Radiator

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

4

5 Define objects to compute the spectral radiance of a planckian radiator

6 and its spectral distribution.

7

8 References

9----------

10- :cite:`CIETC1-482004i` : CIE TC 1-48. (2004). APPENDIX E. INFORMATION ON

11 THE USE OF PLANCK'S EQUATION FOR STANDARD AIR. In CIE 015:2004 Colorimetry,

12 3rd Edition (pp. 77-82). ISBN:978-3-901906-33-6

13- :cite:`Wikipedia2003f` : Wikipedia. (2003). Rayleigh-Jeans law. Retrieved

14 February 12, 2022, from https://en.wikipedia.org/wiki/Rayleigh-Jeans_law

15"""

16

17 from __future__ import annotations

18

19 import typing

20

21 import numpy as np

22

23 from colour.colorimetry import (

24 SPECTRAL_SHAPE_DEFAULT,

25 SpectralDistribution,

26 SpectralShape,

27)

28 from colour.constants import CONSTANT_BOLTZMANN, CONSTANT_LIGHT_SPEED

29

30 if typing.TYPE_CHECKING:

31 from colour.hints import ArrayLike, NDArrayFloat

32

33 from colour.utilities import as_float, as_float_array

34 from colour.utilities.common import attest

35

36 __author__ = "Colour Developers"

37 __copyright__ = "Copyright 2013 Colour Developers"

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

39 __maintainer__ = "Colour Developers"

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

41 __status__ = "Production"

42

43 __all__ = [

44 "CONSTANT_C1",

45 "CONSTANT_C2",

46 "CONSTANT_N",

47 "planck_law",

48 "blackbody_spectral_radiance",

49 "sd_blackbody",

50 "rayleigh_jeans_law",

51 "sd_rayleigh_jeans",

52]

53

54# 2 * math.pi * CONSTANT_PLANCK * CONSTANT_LIGHT_SPEED ** 2

55 CONSTANT_C1: float = 3.741771e-16

56

57# CONSTANT_PLANCK * CONSTANT_LIGHT_SPEED / CONSTANT_BOLTZMANN

58 CONSTANT_C2: float = 1.4388e-2

59

60 CONSTANT_N: float = 1

61

62

63 def planck_law(

64 wavelength: ArrayLike,

65 temperature: ArrayLike,

66 c1: float = CONSTANT_C1,

67 c2: float = CONSTANT_C2,

68 n: float = CONSTANT_N,

69) -> NDArrayFloat:

70 """

71 Compute the spectral radiance of a blackbody as a function of

72 wavelength at specified thermodynamic temperature :math:`T[K]` in a

73 medium with index of refraction :math:`n`.

74

75 Parameters

76 ----------

77 wavelength

78 Wavelength in meters.

79 temperature

80 Temperature :math:`T[K]` in kelvin degrees.

81 c1

82 The official value of :math:`c1` is provided by the Committee on

83 Data for Science and Technology (CODATA) and is

84 :math:`c1=3.741771 \\times 10^{-16}\\ \\mathrm{W/m^2}` *(Mohr and

85 Taylor, 2000)*.

86 c2

87 Since :math:`T` is measured on the International Temperature

88 Scale, the value of :math:`c2` used in colorimetry should follow

89 that adopted in the current International Temperature Scale

90 (ITS-90) *(Preston-Thomas, 1990; Mielenz et al., 1991)*, namely

91 :math:`c2=1.4388 \\times 10^{-2}\\ \\mathrm{m \\cdot K}`.

92 n

93 Medium index of refraction. For dry air at 15°C and 101 325 Pa,

94 containing 0.03 percent by volume of carbon dioxide, it is

95 approximately 1.00028 throughout the visible region although

96 *CIE 15:2004* recommends using :math:`n=1`.

97

98 Returns

99 -------

100 :class:`numpy.ndarray`

101 Radiance in *watts per steradian per square metre*

102 (:math:`\\mathrm{W \\cdot sr^{-1} \\cdot m^{-2}}`).

103

104 Warnings

105 --------

106 The :func:`colour.colorimetry.planck_law` definition behaviour with

107 n-dimensional arrays is unusual: The ``wavelength`` and

108 ``temperature`` parameters are first raveled using

109 :func:`numpy.ravel`. Then, they are *broadcasted* together by

110 transposing the ``temperature`` parameter. Finally, and for

111 convenience, the return value is squeezed using :func:`numpy.squeeze`.

112

113 Notes

114 -----

115 - The following implementation is expressed in terms of wavelength.

116 - The SI unit of radiance is *watts per steradian per square

117 metre* (:math:`\\mathrm{W \\cdot sr^{-1} \\cdot m^{-2}}`).

118

119 References

120 ----------

121 :cite:`CIETC1-482004i`

122

123 Examples

124 --------

125 >>> planck_law(500 * 1e-9, 5500) # doctest: +ELLIPSIS

126 20472701909806.5...

127 >>> planck_law(500 * 1e-9, [5000, 5500, 6000]) # doctest: +ELLIPSIS

128 array([ 1.2106064...e+13, 2.0472701...e+13, 3.1754431...e+13])

129 """

130

131 l = as_float_array(wavelength) # noqa: E741

132 t = as_float_array(temperature)

133

134 attest(np.all(l > 0), "Wavelengths must be positive real numbers!")

135

136 l = np.ravel(l)[..., None] # noqa: E741

137 t = np.ravel(t)[None, ...]

138

139 d = 1 / np.expm1(c2 / (n * l * t))

140 p = ((c1 * n**-2 * l**-5) / np.pi) * d

141

142 return as_float(np.squeeze(p))

143

144

145 blackbody_spectral_radiance = planck_law

146

147

148 def sd_blackbody(

149 temperature: float,

150 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT,

151 c1: float = CONSTANT_C1,

152 c2: float = CONSTANT_C2,

153 n: float = CONSTANT_N,

154) -> SpectralDistribution:

155 """

156 Generate the spectral distribution of the planckian radiator for the

157 specified temperature :math:`T[K]` with values in *watts per steradian

158 per square metre per nanometre* (:math:`W/sr/m^2/nm`).

159

160 Parameters

161 ----------

162 temperature

163 Temperature :math:`T[K]` in kelvins.

164 shape

165 Spectral shape used to create the spectral distribution of the

166 planckian radiator.

167 c1

168 The official value of :math:`c_1` is provided by the Committee on

169 Data for Science and Technology (CODATA) and is

170 :math:`c_1=3.741771 \\times 10^{16}\\ W/m^2` *(Mohr and Taylor,

171 2000)*.

172 c2

173 Since :math:`T` is measured on the International Temperature

174 Scale, the value of :math:`c_2` used in colorimetry should follow

175 that adopted in the current International Temperature Scale

176 (ITS-90) *(Preston-Thomas, 1990; Mielenz et al., 1991)*, namely

177 :math:`c_2=1.4388 \\times 10^{-2}\\ m \\cdot K`.

178 n

179 Medium index of refraction. For dry air at 15°C and 101 325 Pa,

180 containing 0.03 percent by volume of carbon dioxide, it is

181 approximately 1.00028 throughout the visible region although

182 *CIE 15:2004* recommends using :math:`n=1`.

183

184 Returns

185 -------

186 :class:`colour.SpectralDistribution`

187 Blackbody spectral distribution with values in *watts per

188 steradian per square metre per nanometre* (:math:`W/sr/m^2/nm`).

189

190 Examples

191 --------

192 >>> from colour.utilities import numpy_print_options

193 >>> with numpy_print_options(suppress=True):

194 ... sd_blackbody(5000, shape=SpectralShape(400, 700, 20))

195 ... # doctest: +ELLIPSIS

196 SpectralDistribution([[ 400. , 8742.5713329...],

197 [ 420. , 9651.6810212...],

198 [ 440. , 10447.3423137...],

199 [ 460. , 11121.8597759...],

200 [ 480. , 11673.7121534...],

201 [ 500. , 12106.0645344...],

202 [ 520. , 12425.4166118...],

203 [ 540. , 12640.4550541...],

204 [ 560. , 12761.1284859...],

205 [ 580. , 12797.9345572...],

206 [ 600. , 12761.3938171...],

207 [ 620. , 12661.6795247...],

208 [ 640. , 12508.3723863...],

209 [ 660. , 12310.3119640...],

210 [ 680. , 12075.5205176...],

211 [ 700. , 11811.1793602...]],

212 SpragueInterpolator,

213 {},

214 Extrapolator,

215 {'method': 'Constant', 'left': None, 'right': None})

216 """

217

218 return SpectralDistribution(

219 planck_law(shape.wavelengths * 1e-9, temperature, c1, c2, n) * 1e-9,

220 shape.wavelengths,

221 name=f"{temperature}K Blackbody",

222 )

223

224

225 def rayleigh_jeans_law(wavelength: ArrayLike, temperature: ArrayLike) -> NDArrayFloat:

226 """

227 Approximate the spectral radiance of a blackbody as a function of

228 wavelength at specified thermodynamic temperature :math:`T[K]` according

229 to the *Rayleigh-Jeans* law.

230

231 Parameters

232 ----------

233 wavelength

234 Wavelength in meters.

235 temperature

236 Temperature :math:`T[K]` in kelvin degrees.

237

238 Returns

239 -------

240 :class:`numpy.ndarray`

241 Radiance in *watts per steradian per square metre*

242 (:math:`W/sr/m^2`).

243

244 Warnings

245 --------

246 The :func:`colour.colorimetry.rayleigh_jeans_law` definition behaviour

247 with n-dimensional arrays is unusual: The ``wavelength`` and

248 ``temperature`` parameters are first raveled using :func:`numpy.ravel`.

249 Then, they are *broadcasted* together by transposing the

250 ``temperature`` parameter. Finally, and for convenience, the return

251 value is squeezed using :func:`numpy.squeeze`.

252

253 Notes

254 -----

255 - The *Rayleigh-Jeans* law agrees with experimental results at large

256 wavelengths (low frequencies) but strongly disagrees at short

257 wavelengths (high frequencies). This inconsistency between

258 observations and the predictions of classical physics is commonly

259 known as the *ultraviolet catastrophe*.

260 - The following implementation is expressed in terms of wavelength.

261 - The SI unit of radiance is *watts per steradian per square metre*

262 (:math:`W/sr/m^2`).

263

264 References

265 ----------

266 :cite:`Wikipedia2003f`

267

268 Examples

269 --------

270 >>> rayleigh_jeans_law(500 * 1e-9, 5500) # doctest: +ELLIPSIS

271 728478884562351.5...

272 >>> rayleigh_jeans_law(500 * 1e-9, [5000, 5500, 6000])

273 ... # doctest: +ELLIPSIS

274 array([ 6.6225353...e+14, 7.2847888...e+14, 7.9470423...e+14])

275 """

276

277 l = as_float_array(wavelength) # noqa: E741

278 t = as_float_array(temperature)

279

280 l = np.ravel(l)[..., None] # noqa: E741

281 t = np.ravel(t)[None, ...]

282

283 c = CONSTANT_LIGHT_SPEED

284 k_B = CONSTANT_BOLTZMANN

285

286 B = (2 * c * k_B * t) / (l**4)

287

288 return as_float(np.squeeze(B))

289

290

291 def sd_rayleigh_jeans(

292 temperature: float,

293 shape: SpectralShape = SPECTRAL_SHAPE_DEFAULT,

294) -> SpectralDistribution:

295 """

296 Generate the spectral distribution of the planckian radiator for the

297 specified temperature :math:`T[K]` with values in *watts per steradian

298 per square metre per nanometre* (:math:`W/sr/m^2/nm`) according to the

299 *Rayleigh-Jeans* law.

300

301 Parameters

302 ----------

303 temperature

304 Temperature :math:`T[K]` in kelvins.

305 shape

306 Spectral shape used to create the spectral distribution of the

307 planckian radiator.

308

309 Returns

310 -------

311 :class:`colour.SpectralDistribution`

312 Blackbody spectral distribution with values in *watts per steradian

313 per square metre per nanometre* (:math:`W/sr/m^2/nm`).

314

315 Notes

316 -----

317 - The *Rayleigh-Jeans* law agrees with experimental results at large

318 wavelengths (low frequencies) but strongly disagrees at short

319 wavelengths (high frequencies). This inconsistency between

320 observations and the predictions of classical physics is commonly

321 known as the *ultraviolet catastrophe*.

322

323 Examples

324 --------

325 >>> from colour.utilities import numpy_print_options

326 >>> with numpy_print_options(suppress=True):

327 ... sd_rayleigh_jeans(5000, shape=SpectralShape(400, 700, 20))

328 ... # doctest: +ELLIPSIS

329 SpectralDistribution([[ 400. , 1616829.9106941...],

330 [ 420. , 1330169.9688456...],

331 [ 440. , 1104316.5840408...],

332 [ 460. , 924427.7490112...],

333 [ 480. , 779721.2146480...],

334 [ 500. , 662253.5314203...],

335 [ 520. , 566097.0941823...],

336 [ 540. , 486776.1157138...],

337 [ 560. , 420874.0917050...],

338 [ 580. , 365756.7299433...],

339 [ 600. , 319373.8095198...],

340 [ 620. , 280115.7588306...],

341 [ 640. , 246708.6655722...],

342 [ 660. , 218136.6091932...],

343 [ 680. , 193583.6389284...],

344 [ 700. , 172390.0279623...]],

345 SpragueInterpolator,

346 {},

347 Extrapolator,

348 {'method': 'Constant', 'left': None, 'right': None})

349 """

350

351 return SpectralDistribution(

352 rayleigh_jeans_law(shape.wavelengths * 1e-9, temperature) * 1e-9,

353 shape.wavelengths,

354 name=f"{temperature}K Rayleigh-Jeans",

355 )