Coverage for models/rgb/transfer_functions/rimm_romm_rgb.py: 67%

1"""

2 RIMM, ROMM and ERIMM Encodings

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

4

5 Define the *RIMM, ROMM and ERIMM* encodings opto-electrical transfer functions

6(OETF) and electro-optical transfer functions (EOTF).

7

8- :func:`colour.models.cctf_encoding_ROMMRGB`

9- :func:`colour.models.cctf_decoding_ROMMRGB`

10- :func:`colour.models.cctf_encoding_ProPhotoRGB`

11- :func:`colour.models.cctf_decoding_ProPhotoRGB`

12- :func:`colour.models.cctf_encoding_RIMMRGB`

13- :func:`colour.models.cctf_decoding_RIMMRGB`

14- :func:`colour.models.log_encoding_ERIMMRGB`

15- :func:`colour.models.log_decoding_ERIMMRGB`

16

17 References

18----------

19- :cite:`ANSI2003a` : ANSI. (2003). Specification of ROMM RGB (pp. 1-2).

20 http://www.color.org/ROMMRGB.pdf

21- :cite:`Spaulding2000b` : Spaulding, K. E., Woolfe, G. J., & Giorgianni, E.

22 J. (2000). Reference Input/Output Medium Metric RGB Color Encodings

23 (RIMM/ROMM RGB) (pp. 1-8). http://www.photo-lovers.org/pdf/color/romm.pdf

24"""

25

26 from __future__ import annotations

27

28 import typing

29

30 import numpy as np

31

32 from colour.algebra import spow

33

34 if typing.TYPE_CHECKING:

35 from colour.hints import NDArrayReal

36

37 from colour.hints import ( # noqa: TC001

38 Annotated,

39 Domain1,

40 Range1,

41)

42 from colour.utilities import (

43 as_float,

44 as_float_scalar,

45 as_int,

46 copy_definition,

47 domain_range_scale,

48 from_range_1,

49 to_domain_1,

50)

51

52 __author__ = "Colour Developers"

53 __copyright__ = "Copyright 2013 Colour Developers"

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

55 __maintainer__ = "Colour Developers"

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

57 __status__ = "Production"

58

59 __all__ = [

60 "cctf_encoding_ROMMRGB",

61 "cctf_decoding_ROMMRGB",

62 "cctf_encoding_ProPhotoRGB",

63 "cctf_decoding_ProPhotoRGB",

64 "cctf_encoding_RIMMRGB",

65 "cctf_decoding_RIMMRGB",

66 "log_encoding_ERIMMRGB",

67 "log_decoding_ERIMMRGB",

68]

69

70

71 def cctf_encoding_ROMMRGB(

72 X: Domain1, bit_depth: int = 8, out_int: bool = False

73) -> Annotated[NDArrayReal, 1]:

74 """

75 Apply the *ROMM RGB* encoding colour component transfer function

76 (Encoding CCTF).

77

78 Parameters

79 ----------

80 X

81 Linear data :math:`X_{ROMM}`.

82 bit_depth

83 Bit-depth used for conversion.

84 out_int

85 Whether to return value as integer code value or floating point

86 equivalent of a code value at a specified bit-depth.

87

88 Returns

89 -------

90 :class:`numpy.ndarray`

91 Non-linear encoded data :math:`X'_{ROMM}`.

92

93 Notes

94 -----

95 +------------+-----------------------+---------------+

96 | **Domain** | **Scale - Reference** | **Scale - 1** |

97 +============+=======================+===============+

98 | ``X`` | 1 | 1 |

99 +------------+-----------------------+---------------+

100

101 +------------+-----------------------+---------------+

102 | **Range** | **Scale - Reference** | **Scale - 1** |

103 +============+=======================+===============+

104 | ``X_p`` | 1 | 1 |

105 +------------+-----------------------+---------------+

106

107 - This definition has an output int switch, thus the domain-range

108 scale information is only specified for the floating point mode.

109

110 References

111 ----------

112 :cite:`ANSI2003a`, :cite:`Spaulding2000b`

113

114 Examples

115 --------

116 >>> cctf_encoding_ROMMRGB(0.18) # doctest: +ELLIPSIS

117 0.3857114...

118 >>> cctf_encoding_ROMMRGB(0.18, out_int=True)

119 98

120 """

121

122 X = to_domain_1(X)

123

124 I_max = 2**bit_depth - 1

125

126 E_t = 16 ** (1.8 / (1 - 1.8))

127

128 X_p = np.where(E_t > X, X * 16 * I_max, spow(X, 1 / 1.8) * I_max)

129

130 if out_int:

131 return as_int(np.round(X_p))

132

133 return as_float(from_range_1(X_p / I_max))

134

135

136 def cctf_decoding_ROMMRGB(

137 X_p: Domain1,

138 bit_depth: int = 8,

139 in_int: bool = False,

140) -> Range1:

141 """

142 Apply the *ROMM RGB* decoding colour component transfer function

143 (Decoding CCTF).

144

145 Parameters

146 ----------

147 X_p

148 Non-linear encoded data :math:`X'_{ROMM}`.

149 bit_depth

150 Bit-depth used for conversion.

151 in_int

152 Whether to treat the input value as integer code value or floating

153 point equivalent of a code value at a specified bit-depth.

154

155 Returns

156 -------

157 :class:`numpy.ndarray`

158 Linear data :math:`X_{ROMM}`.

159

160 Notes

161 -----

162 +------------+-----------------------+---------------+

163 | **Domain** | **Scale - Reference** | **Scale - 1** |

164 +============+=======================+===============+

165 | ``X_p`` | 1 | 1 |

166 +------------+-----------------------+---------------+

167

168 +------------+-----------------------+---------------+

169 | **Range** | **Scale - Reference** | **Scale - 1** |

170 +============+=======================+===============+

171 | ``X`` | 1 | 1 |

172 +------------+-----------------------+---------------+

173

174 - This definition has an input int switch, thus the domain-range

175 scale information is only specified for the floating point mode.

176

177 References

178 ----------

179 :cite:`ANSI2003a`, :cite:`Spaulding2000b`

180

181 Examples

182 --------

183 >>> cctf_decoding_ROMMRGB(0.385711424751138) # doctest: +ELLIPSIS

184 0.1...

185 >>> cctf_decoding_ROMMRGB(98, in_int=True) # doctest: +ELLIPSIS

186 0.1...

187 """

188

189 X_p = to_domain_1(X_p)

190

191 I_max = 2**bit_depth - 1

192

193 if not in_int:

194 X_p = X_p * I_max

195

196 E_t = 16 ** (1.8 / (1 - 1.8))

197

198 X = np.where(

199 X_p < 16 * E_t * I_max,

200 X_p / (16 * I_max),

201 spow(X_p / I_max, 1.8),

202 )

203

204 return as_float(from_range_1(X))

205

206

207 cctf_encoding_ProPhotoRGB = copy_definition(

208 cctf_encoding_ROMMRGB, "cctf_encoding_ProPhotoRGB"

209)

210# If-clause required for optimised python launch.

211 if cctf_encoding_ProPhotoRGB.__doc__ is not None:

212 cctf_encoding_ProPhotoRGB.__doc__ = cctf_encoding_ProPhotoRGB.__doc__.replace(

213 "*ROMM RGB*", "*ProPhoto RGB*"

214 )

215 cctf_decoding_ProPhotoRGB = copy_definition(

216 cctf_decoding_ROMMRGB, "cctf_decoding_ProPhotoRGB"

217)

218# If-clause required for optimised python launch.

219 if cctf_decoding_ProPhotoRGB.__doc__ is not None:

220 cctf_decoding_ProPhotoRGB.__doc__ = cctf_decoding_ProPhotoRGB.__doc__.replace(

221 "*ROMM RGB*", "*ProPhoto RGB*"

222 )

223

224

225 def cctf_encoding_RIMMRGB(

226 X: Domain1,

227 bit_depth: int = 8,

228 out_int: bool = False,

229 E_clip: float = 2.0,

230) -> Annotated[NDArrayReal, 1]:

231 """

232 Apply the *RIMM RGB* encoding colour component transfer function

233 (Encoding CCTF).

234

235 Parameters

236 ----------

237 X

238 Linear data :math:`X_{RIMM}`.

239 bit_depth

240 Bit-depth used for conversion.

241 out_int

242 Whether to return value as integer code value or floating point

243 equivalent of a code value at a specified bit-depth.

244 E_clip

245 Maximum exposure limit.

246

247 Returns

248 -------

249 :class:`numpy.ndarray`

250 Non-linear encoded data :math:`X'_{RIMM}`.

251

252 Notes

253 -----

254 +------------+-----------------------+---------------+

255 | **Domain** | **Scale - Reference** | **Scale - 1** |

256 +============+=======================+===============+

257 | ``X`` | 1 | 1 |

258 +------------+-----------------------+---------------+

259

260 +------------+-----------------------+---------------+

261 | **Range** | **Scale - Reference** | **Scale - 1** |

262 +============+=======================+===============+

263 | ``X_p`` | 1 | 1 |

264 +------------+-----------------------+---------------+

265

266 - This definition has an output int switch, thus the domain-range

267 scale information is only specified for the floating point mode.

268

269 References

270 ----------

271 :cite:`Spaulding2000b`

272

273 Examples

274 --------

275 >>> cctf_encoding_RIMMRGB(0.18) # doctest: +ELLIPSIS

276 0.2916737...

277 >>> cctf_encoding_RIMMRGB(0.18, out_int=True)

278 74

279 """

280

281 X = to_domain_1(X)

282

283 I_max = 2**bit_depth - 1

284

285 V_clip = 1.099 * spow(E_clip, 0.45) - 0.099

286 q = I_max / V_clip

287

288 X_p = q * np.select(

289 [X < 0.0, X < 0.018, X >= 0.018, E_clip < X],

290 [0, 4.5 * X, 1.099 * spow(X, 0.45) - 0.099, I_max],

291 )

292

293 if out_int:

294 return as_int(np.round(X_p))

295

296 return as_float(from_range_1(X_p / I_max))

297

298

299 def cctf_decoding_RIMMRGB(

300 X_p: Domain1,

301 bit_depth: int = 8,

302 in_int: bool = False,

303 E_clip: float = 2.0,

304) -> Range1:

305 """

306 Apply the *RIMM RGB* decoding colour component transfer function

307 (Decoding CCTF).

308

309 Parameters

310 ----------

311 X_p

312 Non-linear encoded data :math:`X'_{RIMM}`.

313 bit_depth

314 Bit-depth used for conversion.

315 in_int

316 Whether to treat the input value as integer code value or floating

317 point equivalent of a code value at a specified bit-depth.

318 E_clip

319 Maximum exposure limit.

320

321 Returns

322 -------

323 :class:`numpy.ndarray`

324 Linear data :math:`X_{RIMM}`.

325

326 Notes

327 -----

328 +------------+-----------------------+---------------+

329 | **Domain** | **Scale - Reference** | **Scale - 1** |

330 +============+=======================+===============+

331 | ``X_p`` | 1 | 1 |

332 +------------+-----------------------+---------------+

333

334 +------------+-----------------------+---------------+

335 | **Range** | **Scale - Reference** | **Scale - 1** |

336 +============+=======================+===============+

337 | ``X`` | 1 | 1 |

338 +------------+-----------------------+---------------+

339

340 - This definition has an input int switch, thus the domain-range

341 scale information is only specified for the floating point mode.

342

343 References

344 ----------

345 :cite:`Spaulding2000b`

346

347 Examples

348 --------

349 >>> cctf_decoding_RIMMRGB(0.291673732475746) # doctest: +ELLIPSIS

350 0.1...

351 >>> cctf_decoding_RIMMRGB(74, in_int=True) # doctest: +ELLIPSIS

352 0.1...

353 """

354

355 X_p = to_domain_1(X_p)

356

357 I_max = as_float_scalar(2**bit_depth - 1)

358

359 if not in_int:

360 X_p = X_p * I_max

361

362 V_clip = 1.099 * spow(E_clip, 0.45) - 0.099

363

364 m = V_clip * X_p / I_max

365

366 with domain_range_scale("ignore"):

367 X = np.where(

368 X_p / I_max < cctf_encoding_RIMMRGB(0.018, bit_depth, E_clip=E_clip),

369 m / 4.5,

370 spow((m + 0.099) / 1.099, 1 / 0.45),

371 )

372

373 return as_float(from_range_1(X))

374

375

376 def log_encoding_ERIMMRGB(

377 X: Domain1,

378 bit_depth: int = 8,

379 out_int: bool = False,

380 E_min: float = 0.001,

381 E_clip: float = 316.2,

382) -> Annotated[NDArrayReal, 1]:

383 """

384 Apply the *ERIMM RGB* log encoding opto-electronic transfer function (OETF).

385

386 Parameters

387 ----------

388 X

389 Linear data :math:`X_{ERIMM}`.

390 bit_depth

391 Bit-depth used for conversion.

392 out_int

393 Whether to return value as integer code value or floating point

394 equivalent of a code value at a specified bit-depth.

395 E_min

396 Minimum exposure limit.

397 E_clip

398 Maximum exposure limit.

399

400 Returns

401 -------

402 :class:`numpy.ndarray`

403 Non-linear encoded data :math:`X'_{ERIMM}`.

404

405 Notes

406 -----

407 +------------+-----------------------+---------------+

408 | **Domain** | **Scale - Reference** | **Scale - 1** |

409 +============+=======================+===============+

410 | ``X`` | 1 | 1 |

411 +------------+-----------------------+---------------+

412

413 +------------+-----------------------+---------------+

414 | **Range** | **Scale - Reference** | **Scale - 1** |

415 +============+=======================+===============+

416 | ``X_p`` | 1 | 1 |

417 +------------+-----------------------+---------------+

418

419 - This definition has an output int switch, thus the domain-range

420 scale information is only specified for the floating point mode.

421

422 References

423 ----------

424 :cite:`Spaulding2000b`

425

426 Examples

427 --------

428 >>> log_encoding_ERIMMRGB(0.18) # doctest: +ELLIPSIS

429 0.4100523...

430 >>> log_encoding_ERIMMRGB(0.18, out_int=True)

431 105

432 """

433

434 X = to_domain_1(X)

435

436 I_max = 2**bit_depth - 1

437

438 E_t = np.exp(1) * E_min

439

440 l_E_t = np.log(E_t)

441 l_E_min = np.log(E_min)

442 l_E_clip = np.log(E_clip)

443 X_p = np.select(

444 [

445 X < 0.0,

446 E_t >= X,

447 E_t < X,

448 E_clip < X,

449 ],

450 [

451 0,

452 I_max * ((l_E_t - l_E_min) / (l_E_clip - l_E_min)) * X / E_t,

453 I_max * ((np.log(X) - l_E_min) / (l_E_clip - l_E_min)),

454 I_max,

455 ],

456 )

457

458 if out_int:

459 return as_int(np.round(X_p))

460

461 return as_float(from_range_1(X_p / I_max))

462

463

464 def log_decoding_ERIMMRGB(

465 X_p: Domain1,

466 bit_depth: int = 8,

467 in_int: bool = False,

468 E_min: float = 0.001,

469 E_clip: float = 316.2,

470) -> Range1:

471 """

472 Apply the *ERIMM RGB* log decoding inverse opto-electronic transfer function (OETF).

473

474 Parameters

475 ----------

476 X_p

477 Non-linear encoded data :math:`X'_{ERIMM}`.

478 bit_depth

479 Bit-depth used for conversion.

480 in_int

481 Whether to treat the input value as integer code value or floating

482 point equivalent of a code value at a specified bit-depth.

483 E_min

484 Minimum exposure limit.

485 E_clip

486 Maximum exposure limit.

487

488 Returns

489 -------

490 :class:`numpy.ndarray`

491 Linear data :math:`X_{ERIMM}`.

492

493 Notes

494 -----

495 +------------+-----------------------+---------------+

496 | **Domain** | **Scale - Reference** | **Scale - 1** |

497 +============+=======================+===============+

498 | ``X_p`` | 1 | 1 |

499 +------------+-----------------------+---------------+

500

501 +------------+-----------------------+---------------+

502 | **Range** | **Scale - Reference** | **Scale - 1** |

503 +============+=======================+===============+

504 | ``X`` | 1 | 1 |

505 +------------+-----------------------+---------------+

506

507 - This definition has an input int switch, thus the domain-range

508 scale information is only specified for the floating point mode.

509

510 References

511 ----------

512 :cite:`Spaulding2000b`

513

514 Examples

515 --------

516 >>> log_decoding_ERIMMRGB(0.410052389492129) # doctest: +ELLIPSIS

517 0.1...

518 >>> log_decoding_ERIMMRGB(105, in_int=True) # doctest: +ELLIPSIS

519 0.1...

520 """

521

522 X_p = to_domain_1(X_p)

523

524 I_max = 2**bit_depth - 1

525

526 if not in_int:

527 X_p = X_p * I_max

528

529 E_t = np.exp(1) * E_min

530

531 l_E_t = np.log(E_t)

532 l_E_min = np.log(E_min)

533 l_E_clip = np.log(E_clip)

534 X = np.where(

535 X_p <= I_max * ((l_E_t - l_E_min) / (l_E_clip - l_E_min)),

536 ((l_E_clip - l_E_min) / (l_E_t - l_E_min)) * ((X_p * E_t) / I_max),

537 np.exp((X_p / I_max) * (l_E_clip - l_E_min) + l_E_min),

538 )

539

540 return as_float(from_range_1(X))