Coverage for models/rgb/transfer_functions/aces.py: 59%

64 statements  

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

1""" 

2Academy Color Encoding System - Log Encodings 

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

4 

5Define the *Academy Color Encoding System* (ACES) log encodings. 

6 

7- :func:`colour.models.log_encoding_ACESproxy` 

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

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

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

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

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

13 

14References 

15---------- 

16- :cite:`TheAcademyofMotionPictureArtsandSciences2014q` : The Academy of 

17 Motion Picture Arts and Sciences, Science and Technology Council, & Academy 

18 Color Encoding System (ACES) Project Subcommittee. (2014). Technical 

19 Bulletin TB-2014-004 - Informative Notes on SMPTE ST 2065-1 - Academy Color 

20 Encoding Specification (ACES) (pp. 1-40). Retrieved December 19, 2014, from 

21 http://j.mp/TB-2014-004 

22- :cite:`TheAcademyofMotionPictureArtsandSciences2014r` : The Academy of 

23 Motion Picture Arts and Sciences, Science and Technology Council, & Academy 

24 Color Encoding System (ACES) Project Subcommittee. (2014). Technical 

25 Bulletin TB-2014-012 - Academy Color Encoding System Version 1.0 Component 

26 Names (pp. 1-8). Retrieved December 19, 2014, from http://j.mp/TB-2014-012 

27- :cite:`TheAcademyofMotionPictureArtsandSciences2014s` : The Academy of 

28 Motion Picture Arts and Sciences, Science and Technology Council, & Academy 

29 Color Encoding System (ACES) Project Subcommittee. (2013). Specification 

30 S-2013-001 - ACESproxy, an int Log Encoding of ACES Image Data. 

31 Retrieved December 19, 2014, from http://j.mp/S-2013-001 

32- :cite:`TheAcademyofMotionPictureArtsandSciences2014t` : The Academy of 

33 Motion Picture Arts and Sciences, Science and Technology Council, & Academy 

34 Color Encoding System (ACES) Project Subcommittee. (2014). Specification 

35 S-2014-003 - ACEScc, A Logarithmic Encoding of ACES Data for use within 

36 Color Grading Systems (pp. 1-12). Retrieved December 19, 2014, from 

37 http://j.mp/S-2014-003 

38- :cite:`TheAcademyofMotionPictureArtsandSciences2016c` : The Academy of 

39 Motion Picture Arts and Sciences, Science and Technology Council, & Academy 

40 Color Encoding System (ACES) Project. (2016). Specification S-2016-001 - 

41 ACEScct, A Quasi-Logarithmic Encoding of ACES Data for use within Color 

42 Grading Systems. Retrieved October 10, 2016, from http://j.mp/S-2016-001 

43- :cite:`TheAcademyofMotionPictureArtsandSciencese` : The Academy of Motion 

44 Picture Arts and Sciences, Science and Technology Council, & Academy Color 

45 Encoding System (ACES) Project Subcommittee. (n.d.). Academy Color Encoding 

46 System. Retrieved February 24, 2014, from 

47 http://www.oscars.org/science-technology/council/projects/aces.html 

48""" 

49 

50from __future__ import annotations 

51 

52import typing 

53 

54import numpy as np 

55 

56if typing.TYPE_CHECKING: 

57 from colour.hints import Literal, NDArrayInt 

58 

59from colour.hints import ( # noqa: TC001 

60 Annotated, 

61 Domain1, 

62 NDArrayFloat, 

63 Range1, 

64) 

65from colour.utilities import ( 

66 Structure, 

67 as_float, 

68 as_int, 

69 from_range_1, 

70 optional, 

71 to_domain_1, 

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 "CONSTANTS_ACES_PROXY_10", 

83 "CONSTANTS_ACES_PROXY_12", 

84 "CONSTANTS_ACES_PROXY", 

85 "CONSTANTS_ACES_CCT", 

86 "log_encoding_ACESproxy", 

87 "log_decoding_ACESproxy", 

88 "log_encoding_ACEScc", 

89 "log_decoding_ACEScc", 

90 "log_encoding_ACEScct", 

91 "log_decoding_ACEScct", 

92] 

93 

94CONSTANTS_ACES_PROXY_10: Structure = Structure( 

95 CV_min=64, 

96 CV_max=940, 

97 steps_per_stop=50, 

98 mid_CV_offset=425, 

99 mid_log_offset=2.5, 

100) 

101"""*ACESproxy* 10 bit constants.""" 

102 

103CONSTANTS_ACES_PROXY_12: Structure = Structure( 

104 CV_min=256, 

105 CV_max=3760, 

106 steps_per_stop=200, 

107 mid_CV_offset=1700, 

108 mid_log_offset=2.5, 

109) 

110"""*ACESproxy* 12 bit constants.""" 

111 

112CONSTANTS_ACES_PROXY: dict = { 

113 10: CONSTANTS_ACES_PROXY_10, 

114 12: CONSTANTS_ACES_PROXY_12, 

115} 

116"""Aggregated *ACESproxy* constants.""" 

117 

118CONSTANTS_ACES_CCT: Structure = Structure( 

119 X_BRK=0.0078125, 

120 Y_BRK=0.155251141552511, 

121 A=10.5402377416545, 

122 B=0.0729055341958355, 

123) 

124"""*ACEScct* constants.""" 

125 

126 

127def log_encoding_ACESproxy( 

128 lin_AP1: Domain1, 

129 bit_depth: Literal[10, 12] = 10, 

130 out_int: bool = False, 

131 constants: dict | None = None, 

132) -> Annotated[NDArrayFloat | NDArrayInt, 1]: 

133 """ 

134 Apply the *ACESproxy* log encoding opto-electronic transfer function (OETF). 

135 

136 Parameters 

137 ---------- 

138 lin_AP1 

139 Linear *AP1* colourspace value. 

140 bit_depth 

141 *ACESproxy* bit-depth. 

142 out_int 

143 Whether to return value as int code value or float equivalent of a 

144 code value at a specified bit-depth. 

145 constants 

146 *ACESproxy* constants. 

147 

148 Returns 

149 ------- 

150 :class:`numpy.ndarray` 

151 *ACESproxy* non-linear encoded value. 

152 

153 Notes 

154 ----- 

155 +----------------+-----------------------+---------------+ 

156 | **Domain** | **Scale - Reference** | **Scale - 1** | 

157 +================+=======================+===============+ 

158 | ``lin_AP1`` | 1 | 1 | 

159 +----------------+-----------------------+---------------+ 

160 

161 +----------------+-----------------------+---------------+ 

162 | **Range** | **Scale - Reference** | **Scale - 1** | 

163 +================+=======================+===============+ 

164 | ``ACESproxy`` | 1 | 1 | 

165 +----------------+-----------------------+---------------+ 

166 

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

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

169 

170 References 

171 ---------- 

172 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

173 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

174 :cite:`TheAcademyofMotionPictureArtsandSciences2014s`, 

175 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

176 

177 Examples 

178 -------- 

179 >>> log_encoding_ACESproxy(0.18) # doctest: +ELLIPSIS 

180 0.4164222... 

181 >>> log_encoding_ACESproxy(0.18, out_int=True) 

182 426 

183 """ 

184 

185 lin_AP1 = to_domain_1(lin_AP1) 

186 constants = optional(constants, CONSTANTS_ACES_PROXY) 

187 

188 CV_min = constants[bit_depth].CV_min 

189 CV_max = constants[bit_depth].CV_max 

190 mid_CV_offset = constants[bit_depth].mid_CV_offset 

191 mid_log_offset = constants[bit_depth].mid_log_offset 

192 steps_per_stop = constants[bit_depth].steps_per_stop 

193 

194 def float_2_cv(x: float) -> float: 

195 """Convert specified numeric to code value.""" 

196 

197 return np.maximum(CV_min, np.minimum(CV_max, np.round(x))) 

198 

199 ACESproxy = np.where( 

200 lin_AP1 > 2**-9.72, 

201 float_2_cv( 

202 (np.log2(lin_AP1) + mid_log_offset) * steps_per_stop + mid_CV_offset 

203 ), 

204 np.resize(CV_min, lin_AP1.shape), 

205 ) 

206 

207 if out_int: 

208 return as_int(np.round(ACESproxy)) 

209 

210 return as_float(from_range_1(ACESproxy / (2**bit_depth - 1))) 

211 

212 

213def log_decoding_ACESproxy( 

214 ACESproxy: Domain1, 

215 bit_depth: Literal[10, 12] = 10, 

216 in_int: bool = False, 

217 constants: dict | None = None, 

218) -> Range1: 

219 """ 

220 Apply the *ACESproxy* log decoding inverse opto-electronic transfer function (OETF). 

221 

222 Parameters 

223 ---------- 

224 ACESproxy 

225 *ACESproxy* non-linear encoded value. 

226 bit_depth 

227 *ACESproxy* bit-depth. 

228 in_int 

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

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

231 constants 

232 *ACESproxy* constants. 

233 

234 Returns 

235 ------- 

236 :class:`numpy.ndarray` 

237 Linear *AP1* colourspace value. 

238 

239 Notes 

240 ----- 

241 +----------------+-----------------------+---------------+ 

242 | **Domain** | **Scale - Reference** | **Scale - 1** | 

243 +================+=======================+===============+ 

244 | ``ACESproxy`` | 1 | 1 | 

245 +----------------+-----------------------+---------------+ 

246 

247 +----------------+-----------------------+---------------+ 

248 | **Range** | **Scale - Reference** | **Scale - 1** | 

249 +================+=======================+===============+ 

250 | ``lin_AP1`` | 1 | 1 | 

251 +----------------+-----------------------+---------------+ 

252 

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

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

255 

256 References 

257 ---------- 

258 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

259 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

260 :cite:`TheAcademyofMotionPictureArtsandSciences2014s`, 

261 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

262 

263 Examples 

264 -------- 

265 >>> log_decoding_ACESproxy(0.416422287390029) # doctest: +ELLIPSIS 

266 0.1... 

267 >>> log_decoding_ACESproxy(426, in_int=True) # doctest: +ELLIPSIS 

268 0.1... 

269 """ 

270 

271 ACESproxy = to_domain_1(ACESproxy) 

272 constants = optional(constants, CONSTANTS_ACES_PROXY) 

273 

274 mid_CV_offset = constants[bit_depth].mid_CV_offset 

275 mid_log_offset = constants[bit_depth].mid_log_offset 

276 steps_per_stop = constants[bit_depth].steps_per_stop 

277 

278 if not in_int: 

279 ACESproxy = ACESproxy * (2**bit_depth - 1) 

280 

281 lin_AP1 = 2 ** ((ACESproxy - mid_CV_offset) / steps_per_stop - mid_log_offset) 

282 

283 return as_float(from_range_1(lin_AP1)) 

284 

285 

286def log_encoding_ACEScc(lin_AP1: Domain1) -> Range1: 

287 """ 

288 Apply the *ACEScc* log encoding opto-electronic transfer function (OETF). 

289 

290 Parameters 

291 ---------- 

292 lin_AP1 

293 Linear *AP1* colourspace value. 

294 

295 Returns 

296 ------- 

297 :class:`numpy.ndarray` 

298 *ACEScc* non-linear encoded value. 

299 

300 Notes 

301 ----- 

302 +-------------+-----------------------+---------------+ 

303 | **Domain** | **Scale - Reference** | **Scale - 1** | 

304 +=============+=======================+===============+ 

305 | ``lin_AP1`` | 1 | 1 | 

306 +-------------+-----------------------+---------------+ 

307 

308 +-------------+-----------------------+---------------+ 

309 | **Range** | **Scale - Reference** | **Scale - 1** | 

310 +=============+=======================+===============+ 

311 | ``ACEScc`` | 1 | 1 | 

312 +-------------+-----------------------+---------------+ 

313 

314 References 

315 ---------- 

316 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

317 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

318 :cite:`TheAcademyofMotionPictureArtsandSciences2014t`, 

319 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

320 

321 Examples 

322 -------- 

323 >>> log_encoding_ACEScc(0.18) # doctest: +ELLIPSIS 

324 0.4135884... 

325 """ 

326 

327 lin_AP1 = to_domain_1(lin_AP1) 

328 

329 ACEScc = np.where( 

330 lin_AP1 < 0, 

331 (np.log2(2**-16) + 9.72) / 17.52, 

332 (np.log2(2**-16 + lin_AP1 * 0.5) + 9.72) / 17.52, 

333 ) 

334 ACEScc = np.where( 

335 lin_AP1 >= 2**-15, 

336 (np.log2(lin_AP1) + 9.72) / 17.52, 

337 ACEScc, 

338 ) 

339 

340 return as_float(from_range_1(ACEScc)) 

341 

342 

343def log_decoding_ACEScc(ACEScc: Domain1) -> Range1: 

344 """ 

345 Apply the *ACEScc* log decoding inverse opto-electronic transfer function (OETF). 

346 

347 Parameters 

348 ---------- 

349 ACEScc 

350 *ACEScc* non-linear encoded value. 

351 

352 Returns 

353 ------- 

354 :class:`numpy.ndarray` 

355 Linear *AP1* colourspace value. 

356 

357 Notes 

358 ----- 

359 +-------------+-----------------------+---------------+ 

360 | **Domain** | **Scale - Reference** | **Scale - 1** | 

361 +=============+=======================+===============+ 

362 | ``ACEScc`` | 1 | 1 | 

363 +-------------+-----------------------+---------------+ 

364 

365 +-------------+-----------------------+---------------+ 

366 | **Range** | **Scale - Reference** | **Scale - 1** | 

367 +=============+=======================+===============+ 

368 | ``lin_AP1`` | 1 | 1 | 

369 +-------------+-----------------------+---------------+ 

370 

371 References 

372 ---------- 

373 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

374 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

375 :cite:`TheAcademyofMotionPictureArtsandSciences2014t`, 

376 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

377 

378 Examples 

379 -------- 

380 >>> log_decoding_ACEScc(0.413588402492442) # doctest: +ELLIPSIS 

381 0.1799999... 

382 """ 

383 

384 ACEScc = to_domain_1(ACEScc) 

385 

386 lin_AP1 = np.where( 

387 ACEScc < (9.72 - 15) / 17.52, 

388 (2 ** (ACEScc * 17.52 - 9.72) - 2**-16) * 2, 

389 2 ** (ACEScc * 17.52 - 9.72), 

390 ) 

391 lin_AP1 = np.where( 

392 ACEScc >= (np.log2(65504) + 9.72) / 17.52, 

393 65504, 

394 lin_AP1, 

395 ) 

396 

397 return as_float(from_range_1(lin_AP1)) 

398 

399 

400def log_encoding_ACEScct( 

401 lin_AP1: Domain1, constants: Structure | None = None 

402) -> Range1: 

403 """ 

404 Apply the *ACEScct* log encoding opto-electronic transfer function (OETF). 

405 

406 Parameters 

407 ---------- 

408 lin_AP1 

409 Linear *AP1* colourspace value. 

410 constants 

411 *ACEScct* constants. 

412 

413 Returns 

414 ------- 

415 :class:`numpy.ndarray` 

416 *ACEScct* non-linear encoded value. 

417 

418 Notes 

419 ----- 

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

421 | **Domain** | **Scale - Reference** | **Scale - 1** | 

422 +=============+=======================+===============+ 

423 | ``lin_AP1`` | 1 | 1 | 

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

425 

426 +-------------+-----------------------+---------------+ 

427 | **Range** | **Scale - Reference** | **Scale - 1** | 

428 +=============+=======================+===============+ 

429 | ``ACEScct`` | 1 | 1 | 

430 +-------------+-----------------------+---------------+ 

431 

432 References 

433 ---------- 

434 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

435 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

436 :cite:`TheAcademyofMotionPictureArtsandSciences2016c`, 

437 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

438 

439 Examples 

440 -------- 

441 >>> log_encoding_ACEScct(0.18) # doctest: +ELLIPSIS 

442 0.4135884... 

443 """ 

444 

445 lin_AP1 = to_domain_1(lin_AP1) 

446 constants = optional(constants, CONSTANTS_ACES_CCT) 

447 

448 ACEScct = np.where( 

449 lin_AP1 <= constants.X_BRK, 

450 constants.A * lin_AP1 + constants.B, 

451 (np.log2(lin_AP1) + 9.72) / 17.52, 

452 ) 

453 

454 return as_float(from_range_1(ACEScct)) 

455 

456 

457def log_decoding_ACEScct( 

458 ACEScct: Domain1, constants: Structure | None = None 

459) -> Range1: 

460 """ 

461 Apply the *ACEScct* log decoding inverse opto-electronic transfer function (OETF). 

462 

463 Parameters 

464 ---------- 

465 ACEScct 

466 *ACEScct* non-linear encoded value. 

467 constants 

468 *ACEScct* constants. 

469 

470 Returns 

471 ------- 

472 :class:`numpy.ndarray` 

473 Linear *AP1* colourspace value. 

474 

475 Notes 

476 ----- 

477 +-------------+-----------------------+---------------+ 

478 | **Domain** | **Scale - Reference** | **Scale - 1** | 

479 +=============+=======================+===============+ 

480 | ``ACEScct`` | 1 | 1 | 

481 +-------------+-----------------------+---------------+ 

482 

483 +-------------+-----------------------+---------------+ 

484 | **Range** | **Scale - Reference** | **Scale - 1** | 

485 +=============+=======================+===============+ 

486 | ``lin_AP1`` | 1 | 1 | 

487 +-------------+-----------------------+---------------+ 

488 

489 References 

490 ---------- 

491 :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, 

492 :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, 

493 :cite:`TheAcademyofMotionPictureArtsandSciences2016c`, 

494 :cite:`TheAcademyofMotionPictureArtsandSciencese` 

495 

496 Examples 

497 -------- 

498 >>> log_decoding_ACEScct(0.413588402492442) # doctest: +ELLIPSIS 

499 0.1799999... 

500 """ 

501 

502 ACEScct = to_domain_1(ACEScct) 

503 constants = optional(constants, CONSTANTS_ACES_CCT) 

504 

505 lin_AP1 = np.where( 

506 ACEScct > constants.Y_BRK, 

507 2 ** (ACEScct * 17.52 - 9.72), 

508 (ACEScct - constants.B) / constants.A, 

509 ) 

510 

511 return as_float(from_range_1(lin_AP1))