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

76 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2RIMM, ROMM and ERIMM Encodings 

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

4 

5Define 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 

17References 

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 

26from __future__ import annotations 

27 

28import typing 

29 

30import numpy as np 

31 

32from colour.algebra import spow 

33 

34if typing.TYPE_CHECKING: 

35 from colour.hints import NDArrayReal 

36 

37from colour.hints import ( # noqa: TC001 

38 Annotated, 

39 Domain1, 

40 Range1, 

41) 

42from 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 

71def 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 

136def 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 

207cctf_encoding_ProPhotoRGB = copy_definition( 

208 cctf_encoding_ROMMRGB, "cctf_encoding_ProPhotoRGB" 

209) 

210# If-clause required for optimised python launch. 

211if cctf_encoding_ProPhotoRGB.__doc__ is not None: 

212 cctf_encoding_ProPhotoRGB.__doc__ = cctf_encoding_ProPhotoRGB.__doc__.replace( 

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

214 ) 

215cctf_decoding_ProPhotoRGB = copy_definition( 

216 cctf_decoding_ROMMRGB, "cctf_decoding_ProPhotoRGB" 

217) 

218# If-clause required for optimised python launch. 

219if 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 

225def 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 

299def 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 

376def 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 

464def 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))