Coverage for appearance/ciecam16.py: 73%

1"""

2 CIECAM16 Colour Appearance Model

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

4

5 Define the *CIECAM16* colour appearance model for predicting perceptual colour

6 attributes under varying viewing conditions.

7

8- :class:`colour.appearance.InductionFactors_CIECAM16`

9- :attr:`colour.VIEWING_CONDITIONS_CIECAM16`

10- :class:`colour.CAM_Specification_CIECAM16`

11- :func:`colour.XYZ_to_CIECAM16`

12- :func:`colour.CIECAM16_to_XYZ`

13

14 References

15----------

16- :cite:`CIEDivision12022` : CIE Division 1 & CIE Division 8. (2022).

17 CIE 248:2022 The CIE 2016 Colour Appearance Model for Colour Management

18 Systems: CIECAM16. Commission Internationale de l'Eclairage.

19 ISBN:978-3-902842-94-7

20"""

21

22 from __future__ import annotations

23

24 from dataclasses import astuple, dataclass, field

25

26 import numpy as np

27

28 from colour.algebra import spow, vecmul

29 from colour.appearance.cam16 import MATRIX_16, MATRIX_INVERSE_16

30 from colour.appearance.ciecam02 import (

31 VIEWING_CONDITIONS_CIECAM02,

32 InductionFactors_CIECAM02,

33 P,

34 achromatic_response_forward,

35 achromatic_response_inverse,

36 brightness_correlate,

37 chroma_correlate,

38 colourfulness_correlate,

39 degree_of_adaptation,

40 eccentricity_factor,

41 hue_angle,

42 hue_quadrature,

43 lightness_correlate,

44 matrix_post_adaptation_non_linear_response_compression,

45 opponent_colour_dimensions_forward,

46 opponent_colour_dimensions_inverse,

47 post_adaptation_non_linear_response_compression_forward,

48 saturation_correlate,

49 temporary_magnitude_quantity_inverse,

50 viewing_conditions_dependent_parameters,

51)

52 from colour.hints import ( # noqa: TC001

53 Annotated,

54 ArrayLike,

55 Domain100,

56 NDArrayFloat,

57 Range100,

58)

59 from colour.utilities import (

60 CanonicalMapping,

61 MixinDataclassArithmetic,

62 MixinDataclassIterable,

63 as_float,

64 as_float_array,

65 from_range_100,

66 from_range_degrees,

67 has_only_nan,

68 ones,

69 to_domain_100,

70 to_domain_degrees,

71 tsplit,

72)

73

74 __author__ = "Colour Developers"

75 __copyright__ = "Copyright 2013 Colour Developers"

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

77 __maintainer__ = "Colour Developers"

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

79 __status__ = "Production"

80

81 __all__ = [

82 "InductionFactors_CIECAM16",

83 "VIEWING_CONDITIONS_CIECAM16",

84 "CAM_Specification_CIECAM16",

85 "XYZ_to_CIECAM16",

86 "CIECAM16_to_XYZ",

87 "f_e_forward",

88 "f_e_inverse",

89 "f_q",

90 "d_f_q",

91]

92

93

94@dataclass(frozen=True)

95 class InductionFactors_CIECAM16(MixinDataclassIterable):

96 """

97 Define the *CIECAM16* colour appearance model induction factors.

98

99 Parameters

100 ----------

101 F

102 Maximum degree of adaptation :math:`F`.

103 c

104 Exponential non-linearity :math:`c`.

105 N_c

106 Chromatic induction factor :math:`N_c`.

107

108 Notes

109 -----

110 - The *CIECAM16* colour appearance model induction factors are the same

111 as *CIECAM02* colour appearance model.

112

113 References

114 ----------

115 :cite:`CIEDivision12022`

116 """

117

118 F: float

119 c: float

120 N_c: float

121

122

123 VIEWING_CONDITIONS_CIECAM16: CanonicalMapping = CanonicalMapping(

124 VIEWING_CONDITIONS_CIECAM02

125)

126 VIEWING_CONDITIONS_CIECAM16.__doc__ = """

127 Define the reference *CIECAM16* colour appearance model viewing conditions.

128

129 References

130----------

131:cite:`CIEDivision12022`

132"""

133

134

135@dataclass

136 class CAM_Specification_CIECAM16(MixinDataclassArithmetic):

137 """

138 Define the *CIECAM16* colour appearance model specification.

139

140 Parameters

141 ----------

142 J

143 Correlate of *lightness* :math:`J`.

144 C

145 Correlate of *chroma* :math:`C`.

146 h

147 *Hue* angle :math:`h` in degrees.

148 s

149 Correlate of *saturation* :math:`s`.

150 Q

151 Correlate of *brightness* :math:`Q`.

152 M

153 Correlate of *colourfulness* :math:`M`.

154 H

155 *Hue* :math:`h` quadrature :math:`H`.

156 HC

157 *Hue* :math:`h` composition :math:`H^C`.

158

159 References

160 ----------

161 :cite:`CIEDivision12022`

162 """

163

164 J: float | NDArrayFloat | None = field(default_factory=lambda: None)

165 C: float | NDArrayFloat | None = field(default_factory=lambda: None)

166 h: float | NDArrayFloat | None = field(default_factory=lambda: None)

167 s: float | NDArrayFloat | None = field(default_factory=lambda: None)

168 Q: float | NDArrayFloat | None = field(default_factory=lambda: None)

169 M: float | NDArrayFloat | None = field(default_factory=lambda: None)

170 H: float | NDArrayFloat | None = field(default_factory=lambda: None)

171 HC: float | NDArrayFloat | None = field(default_factory=lambda: None)

172

173

174 def XYZ_to_CIECAM16(

175 XYZ: Domain100,

176 XYZ_w: Domain100,

177 L_A: ArrayLike,

178 Y_b: ArrayLike,

179 surround: (

180 InductionFactors_CIECAM02 | InductionFactors_CIECAM16

181 ) = VIEWING_CONDITIONS_CIECAM16["Average"],

182 discount_illuminant: bool = False,

183 compute_H: bool = True,

184) -> Annotated[CAM_Specification_CIECAM16, (100, 100, 360, 100, 100, 100, 400)]:

185 """

186 Compute the *CIECAM16* colour appearance model correlates from the

187 specified *CIE XYZ* tristimulus values.

188

189 Parameters

190 ----------

191 XYZ

192 *CIE XYZ* tristimulus values of test sample / stimulus.

193 XYZ_w

194 *CIE XYZ* tristimulus values of reference white.

195 L_A

196 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often

197 taken to be 20% of the luminance of a white object in the scene).

198 Y_b

199 Luminous factor of background :math:`Y_b` such as

200 :math:`Y_b = 100 \\times L_b / L_w` where :math:`L_w` is the

201 luminance of the light source and :math:`L_b` is the luminance of

202 the background. For viewing images, :math:`Y_b` can be the average

203 :math:`Y` value for the pixels in the entire image, or frequently,

204 a :math:`Y` value of 20, approximate an :math:`L^*` of 50 is used.

205 surround

206 Surround viewing conditions induction factors.

207 discount_illuminant

208 Truth value indicating if the illuminant should be discounted.

209 compute_H

210 Whether to compute *Hue* :math:`h` quadrature :math:`H`.

211 :math:`H` is rarely used, and expensive to compute.

212

213 Returns

214 -------

215 :class:`colour.CAM_Specification_CIECAM16`

216 *CIECAM16* colour appearance model specification.

217

218 Notes

219 -----

220 +---------------------+-----------------------+---------------+

221 | **Domain** | **Scale - Reference** | **Scale - 1** |

222 +=====================+=======================+===============+

223 | ``XYZ`` | 100 | 1 |

224 +---------------------+-----------------------+---------------+

225 | ``XYZ_w`` | 100 | 1 |

226 +---------------------+-----------------------+---------------+

227

228 +---------------------+-----------------------+---------------+

229 | **Range** | **Scale - Reference** | **Scale - 1** |

230 +=====================+=======================+===============+

231 | ``specification.J`` | 100 | 1 |

232 +---------------------+-----------------------+---------------+

233 | ``specification.C`` | 100 | 1 |

234 +---------------------+-----------------------+---------------+

235 | ``specification.h`` | 360 | 1 |

236 +---------------------+-----------------------+---------------+

237 | ``specification.s`` | 100 | 1 |

238 +---------------------+-----------------------+---------------+

239 | ``specification.Q`` | 100 | 1 |

240 +---------------------+-----------------------+---------------+

241 | ``specification.M`` | 100 | 1 |

242 +---------------------+-----------------------+---------------+

243 | ``specification.H`` | 400 | 1 |

244 +---------------------+-----------------------+---------------+

245

246 References

247 ----------

248 :cite:`CIEDivision12022`

249

250 Examples

251 --------

252 >>> XYZ = np.array([19.01, 20.00, 21.78])

253 >>> XYZ_w = np.array([95.05, 100.00, 108.88])

254 >>> L_A = 318.31

255 >>> Y_b = 20.0

256 >>> surround = VIEWING_CONDITIONS_CIECAM16["Average"]

257 >>> XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround) # doctest: +ELLIPSIS

258 CAM_Specification_CIECAM16(J=41.7312079..., C=0.1033557..., \

259 h=217.0679597..., s=2.3450150..., Q=195.3717089..., M=0.1074367..., \

260 H=275.5949861..., HC=None)

261 """

262

263 XYZ = to_domain_100(XYZ)

264 XYZ_w = to_domain_100(XYZ_w)

265 _X_w, Y_w, _Z_w = tsplit(XYZ_w)

266 L_A = as_float_array(L_A)

267 Y_b = as_float_array(Y_b)

268

269 # Step 0

270 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values.

271 RGB_w = vecmul(MATRIX_16, XYZ_w)

272

273 # Computing degree of adaptation :math:`D`.

274 D = (

275 np.clip(degree_of_adaptation(surround.F, L_A), 0, 1)

276 if not discount_illuminant

277 else ones(L_A.shape)

278 )

279

280 n, F_L, N_bb, N_cb, z = viewing_conditions_dependent_parameters(Y_b, Y_w, L_A)

281

282 D_RGB = D[..., None] * 100 / RGB_w + 1 - D[..., None]

283 RGB_wc = D_RGB * RGB_w

284

285 # Applying forward post-adaptation non-linear response compression.

286 RGB_aw = post_adaptation_non_linear_response_compression_forward(RGB_wc, F_L)

287

288 # Computing achromatic responses for the whitepoint.

289 A_w = achromatic_response_forward(RGB_aw, N_bb)

290

291 # Step 1

292 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values.

293 RGB = vecmul(MATRIX_16, XYZ)

294

295 # Step 2

296 RGB_c = D_RGB * RGB

297

298 # Step 3

299 # Applying forward post-adaptation non-linear response compression.

300 RGB_a = f_e_forward(RGB_c, F_L) + 0.1

301

302 # Step 4

303 # Converting to preliminary cartesian coordinates.

304 a, b = tsplit(opponent_colour_dimensions_forward(RGB_a))

305

306 # Computing the *hue* angle :math:`h`.

307 h = hue_angle(a, b)

308

309 # Step 5

310 # Computing eccentricity factor *e_t*.

311 e_t = eccentricity_factor(h)

312

313 # Computing hue :math:`h` quadrature :math:`H`.

314 H = hue_quadrature(h) if compute_H else np.full(h.shape, np.nan)

315 # TODO: Compute hue composition.

316

317 # Step 6

318 # Computing achromatic responses for the stimulus.

319 A = achromatic_response_forward(RGB_a, N_bb)

320

321 # Step 7

322 # Computing the correlate of *Lightness* :math:`J`.

323 J = lightness_correlate(A, A_w, surround.c, z)

324

325 # Step 8

326 # Computing the correlate of *brightness* :math:`Q`.

327 Q = brightness_correlate(surround.c, J, A_w, F_L)

328

329 # Step 9

330 # Computing the correlate of *chroma* :math:`C`.

331 C = chroma_correlate(J, n, surround.N_c, N_cb, e_t, a, b, RGB_a)

332

333 # Computing the correlate of *colourfulness* :math:`M`.

334 M = colourfulness_correlate(C, F_L)

335

336 # Computing the correlate of *saturation* :math:`s`.

337 s = saturation_correlate(M, Q)

338

339 return CAM_Specification_CIECAM16(

340 J=as_float(from_range_100(J)),

341 C=as_float(from_range_100(C)),

342 h=as_float(from_range_degrees(h)),

343 s=as_float(from_range_100(s)),

344 Q=as_float(from_range_100(Q)),

345 M=as_float(from_range_100(M)),

346 H=as_float(from_range_degrees(H, 400)),

347 HC=None,

348 )

349

350

351 def CIECAM16_to_XYZ(

352 specification: Annotated[

353 CAM_Specification_CIECAM16, (100, 100, 360, 100, 100, 100, 400)

354 ],

355 XYZ_w: Domain100,

356 L_A: ArrayLike,

357 Y_b: ArrayLike,

358 surround: (

359 InductionFactors_CIECAM02 | InductionFactors_CIECAM16

360 ) = VIEWING_CONDITIONS_CIECAM16["Average"],

361 discount_illuminant: bool = False,

362) -> Range100:

363 """

364 Convert the *CIECAM16* colour appearance model specification to *CIE XYZ*

365 tristimulus values.

366

367 Parameters

368 ----------

369 specification

370 *CIECAM16* colour appearance model specification. Correlate of

371 *lightness* :math:`J`, correlate of *chroma* :math:`C` or correlate of

372 *colourfulness* :math:`M` and *hue* angle :math:`h` in degrees must be

373 specified, e.g., :math:`JCh` or :math:`JMh`.

374 XYZ_w

375 *CIE XYZ* tristimulus values of reference white.

376 L_A

377 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken

378 to be 20% of the luminance of a white object in the scene).

379 Y_b

380 Luminous factor of background :math:`Y_b` such as

381 :math:`Y_b = 100 \\times L_b / L_w` where :math:`L_w` is the luminance

382 of the light source and :math:`L_b` is the luminance of the background.

383 For viewing images, :math:`Y_b` can be the average :math:`Y` value for

384 the pixels in the entire image, or frequently, a :math:`Y` value of 20,

385 approximating an :math:`L^*` of 50 is used.

386 surround

387 Surround viewing conditions.

388 discount_illuminant

389 Discount the illuminant.

390

391 Returns

392 -------

393 :class:`numpy.ndarray`

394 *CIE XYZ* tristimulus values.

395

396 Raises

397 ------

398 ValueError

399 If neither :math:`C` or :math:`M` correlates have been defined in the

400 ``specification`` argument.

401

402 Notes

403 -----

404 +---------------------+-----------------------+---------------+

405 | **Domain** | **Scale - Reference** | **Scale - 1** |

406 +=====================+=======================+===============+

407 | ``specification.J`` | 100 | 1 |

408 +---------------------+-----------------------+---------------+

409 | ``specification.C`` | 100 | 1 |

410 +---------------------+-----------------------+---------------+

411 | ``specification.h`` | 360 | 1 |

412 +---------------------+-----------------------+---------------+

413 | ``specification.s`` | 100 | 1 |

414 +---------------------+-----------------------+---------------+

415 | ``specification.Q`` | 100 | 1 |

416 +---------------------+-----------------------+---------------+

417 | ``specification.M`` | 100 | 1 |

418 +---------------------+-----------------------+---------------+

419 | ``specification.H`` | 360 | 1 |

420 +---------------------+-----------------------+---------------+

421 | ``XYZ_w`` | 100 | 1 |

422 +---------------------+-----------------------+---------------+

423

424 +---------------------+-----------------------+---------------+

425 | **Range** | **Scale - Reference** | **Scale - 1** |

426 +=====================+=======================+===============+

427 | ``XYZ`` | 100 | 1 |

428 +---------------------+-----------------------+---------------+

429

430 References

431 ----------

432 :cite:`CIEDivision12022`

433

434 Examples

435 --------

436 >>> specification = CAM_Specification_CIECAM16(

437 ... J=41.731207905126638, C=0.103355738709070, h=217.067959767393010

438 ... )

439 >>> XYZ_w = np.array([95.05, 100.00, 108.88])

440 >>> L_A = 318.31

441 >>> Y_b = 20.0

442 >>> CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b) # doctest: +ELLIPSIS

443 array([ 19.01..., 20... , 21.78...])

444 """

445

446 J, C, h, _s, _Q, M, _H, _HC = astuple(specification)

447

448 J = to_domain_100(J)

449 C = to_domain_100(C)

450 h = to_domain_degrees(h)

451 M = to_domain_100(M)

452 L_A = as_float_array(L_A)

453 XYZ_w = to_domain_100(XYZ_w)

454 _X_w, Y_w, _Z_w = tsplit(XYZ_w)

455

456 # Step 0

457 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values.

458 RGB_w = vecmul(MATRIX_16, XYZ_w)

459

460 # Computing degree of adaptation :math:`D`.

461 D = (

462 np.clip(degree_of_adaptation(surround.F, L_A), 0, 1)

463 if not discount_illuminant

464 else ones(L_A.shape)

465 )

466

467 n, F_L, N_bb, N_cb, z = viewing_conditions_dependent_parameters(Y_b, Y_w, L_A)

468

469 D_RGB = D[..., None] * 100 / RGB_w + 1 - D[..., None]

470 RGB_wc = D_RGB * RGB_w

471

472 # Applying forward post-adaptation non-linear response compression.

473 RGB_aw = post_adaptation_non_linear_response_compression_forward(RGB_wc, F_L)

474

475 # Computing achromatic responses for the whitepoint.

476 A_w = achromatic_response_forward(RGB_aw, N_bb)

477

478 # Step 1

479 if has_only_nan(C) and not has_only_nan(M):

480 C = M / spow(F_L, 0.25)

481 elif has_only_nan(C):

482 error = (

483 'Either "C" or "M" correlate must be defined in '

484 'the "CAM_Specification_CIECAM16" argument!'

485 )

486

487 raise ValueError(error)

488

489 # Step 2

490 # Computing temporary magnitude quantity :math:`t`.

491 t = temporary_magnitude_quantity_inverse(C, J, n)

492

493 # Computing eccentricity factor *e_t*.

494 e_t = eccentricity_factor(h)

495

496 # Computing achromatic response :math:`A` for the stimulus.

497 A = achromatic_response_inverse(A_w, J, surround.c, z)

498

499 # Computing *P_1* to *P_3*.

500 P_n = P(surround.N_c, N_cb, e_t, t, A, N_bb)

501 _P_1, P_2, _P_3 = tsplit(P_n)

502

503 # Step 3

504 # Computing opponent colour dimensions :math:`a` and :math:`b`.

505 ab = opponent_colour_dimensions_inverse(P_n, h)

506 a, b = tsplit(ab) * np.where(t == 0, 0, 1)

507

508 # Step 4

509 # Applying post-adaptation non-linear response compression matrix.

510 RGB_a = matrix_post_adaptation_non_linear_response_compression(P_2, a, b)

511

512 # Step 5

513 # Applying inverse post-adaptation non-linear response compression.

514 RGB_c = f_e_inverse(RGB_a - 0.1, F_L)

515

516 # Step 6

517 RGB = RGB_c / D_RGB

518

519 # Step 7

520 XYZ = vecmul(MATRIX_INVERSE_16, RGB)

521

522 return from_range_100(XYZ)

523

524

525 def f_e_forward(RGB_c: ArrayLike, F_L: ArrayLike) -> NDArrayFloat:

526 """

527 Compute the post-adaptation cone responses.

528

529 Parameters

530 ----------

531 RGB_c

532 *CMCCAT2000* transform sharpened :math:`RGB_c` array.

533 F_L

534 *Luminance* level adaptation factor :math:`F_L`.

535

536 Returns

537 -------

538 :class:`numpy.ndarray`

539 Compressed *CMCCAT2000* transform sharpened :math:`RGB_a` array.

540

541 Notes

542 -----

543 - This definition is different from :cite:`Li2017` and provides linear

544 extensions under 0.26 and above 150. It also omits the 0.1 offset

545 that is now part of the general model.

546

547 Examples

548 --------

549 >>> RGB_c = np.array([19.99693975, 20.00186123, 20.01350530])

550 >>> F_L = 1.16754446415

551 >>> f_e_forward(RGB_c, F_L)

552 ... # doctest: +ELLIPSIS

553 array([ 7.8463202..., 7.8471152..., 7.8489959...])

554 """

555

556 RGB_c = as_float_array(RGB_c)

557 F_L = as_float_array(F_L)

558 q_L, q_U = 0.26, 150

559

560 f_q_F_L_q_U = f_q(F_L, q_U)[..., None]

561 f_q_F_L_q_L = f_q(F_L, q_L)[..., None]

562 f_q_F_L_RGB_c = f_q(F_L[..., None], RGB_c)

563 d_f_q_F_L_q_U = d_f_q(F_L, q_U)[..., None]

564

565 return np.select(

566 [

567 RGB_c > q_U,

568 np.logical_and(q_L <= RGB_c, RGB_c <= q_U),

569 RGB_c < q_L,

570 ],

571 [

572 f_q_F_L_q_U + d_f_q_F_L_q_U * (RGB_c - q_U),

573 f_q_F_L_RGB_c,

574 f_q_F_L_q_L * RGB_c / q_L,

575 ],

576 )

577

578

579 def f_e_inverse(RGB_a: ArrayLike, F_L: ArrayLike) -> NDArrayFloat:

580 """

581 Compute the inverse of the forward eccentricity factor modified cone-like

582 responses.

583

584 Parameters

585 ----------

586 RGB_a

587 *CMCCAT2000* transform sharpened :math:`RGB_a` array.

588 F_L

589 *Luminance* level adaptation factor :math:`F_L`.

590

591 Returns

592 -------

593 :class:`numpy.ndarray`

594 Compressed *CMCCAT2000* transform sharpened :math:`RGB_c` array.

595

596 Notes

597 -----

598 - This definition is different from :cite:`Li2017` and provides linear

599 extensions under 0.26 and above 150. It also omits the 0.1 offset

600 that is now part of the general model.

601

602 Examples

603 --------

604 >>> RGB_a = np.array([7.8463202, 7.84711528, 7.84899595])

605 >>> F_L = 1.16754446415

606 >>> f_e_inverse(RGB_a, F_L)

607 ... # doctest: +ELLIPSIS

608 array([ 19.9969397..., 20.0018612..., 20.0135052...])

609 """

610

611 RGB_a = as_float_array(RGB_a)

612 F_L = as_float_array(F_L)

613 q_L, q_U = 0.26, 150

614

615 f_q_F_L_q_U = f_q(F_L, q_U)[..., None]

616 f_q_F_L_q_L = f_q(F_L, q_L)[..., None]

617 d_f_q_F_L_q_U = d_f_q(F_L, q_U)[..., None]

618

619 return np.select(

620 [

621 RGB_a > f_q_F_L_q_U,

622 np.logical_and(f_q_F_L_q_L <= RGB_a, RGB_a <= f_q_F_L_q_U),

623 RGB_a < f_q_F_L_q_L,

624 ],

625 [

626 q_U + (RGB_a - f_q_F_L_q_U) / d_f_q_F_L_q_U,

627 100 / F_L[..., None] * spow((27.13 * RGB_a) / (400 - RGB_a), 1 / 0.42),

628 q_L * RGB_a / f_q_F_L_q_L,

629 ],

630 )

631

632

633 def f_q(F_L: ArrayLike, q: ArrayLike) -> NDArrayFloat:

634 """

635 Evaluate the :math:`f(q)` function for chromatic adaptation.

636

637 Parameters

638 ----------

639 F_L

640 *Luminance* level adaptation factor :math:`F_L`.

641 q

642 :math:`q` parameter.

643

644 Returns

645 -------

646 :class:`numpy.ndarray`

647 Evaluated :math:`f(q)` function result.

648

649 Examples

650 --------

651 >>> f_q(1.17, 0.26) # doctest: +ELLIPSIS

652 1.2886520...

653 """

654

655 F_L = as_float_array(F_L)

656 q = as_float_array(q)

657

658 F_L_q_100 = spow((F_L * q) / 100, 0.42)

659

660 return (400 * F_L_q_100) / (27.13 + F_L_q_100)

661

662

663 def d_f_q(F_L: ArrayLike, q: ArrayLike) -> NDArrayFloat:

664 """

665 Compute the :math:`f'(q)` function derivative.

666

667 Parameters

668 ----------

669 F_L

670 *Luminance* level adaptation factor :math:`F_L`.

671 q

672 :math:`q` parameter.

673

674 Returns

675 -------

676 :class:`numpy.ndarray`

677 Evaluated :math:`f'(q)` function derivative.

678

679 Examples

680 --------

681 >>> d_f_q(1.17, 0.26) # doctest: +ELLIPSIS

682 2.0749623...

683 """

684

685 F_L = as_float_array(F_L)

686 q = as_float_array(q)

687

688 F_L_q_100 = (F_L * q) / 100

689

690 return (1.68 * 27.13 * F_L * spow(F_L_q_100, -0.58)) / (

691 27.13 + spow(F_L_q_100, 0.42)

692 ) ** 2