Coverage for models/rgb/transfer_functions/log.py: 70%

63 statements  

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

1""" 

2Common Log Encodings 

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

4 

5Define the common log encodings. 

6 

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

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

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

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

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

12 

13References 

14---------- 

15- :cite:`TheAcademyofMotionPictureArtsandSciencesa` : 

16 The Academy of Motion Picture Arts and Sciences, 

17 Science and Technology Council, 

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

19 ACESutil.Lin_to_Log2_param.ctl. Retrieved June 14, 2020, 

20 from https://github.com/ampas/aces-dev/blob/\ 

21518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\ 

22ACESutil.Lin_to_Log2_param.ctl 

23- :cite:`TheAcademyofMotionPictureArtsandSciencesb` : 

24 The Academy of Motion Picture Arts and Sciences, 

25 Science and Technology Council, 

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

27 ACESutil.Log2_to_Lin_param.ctl. Retrieved June 14, 2020, 

28 from https://github.com/ampas/aces-dev/blob/\ 

29518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\ 

30ACESutil.Log2_to_Lin_param.ctl 

31: cite: `TheAcademyofMotionPictureArtsandSciences2020` : The Academy of 

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

33 Color Encoding System (ACES) Project Subcommittee. (2020). Specification 

34 S-2014-006 - Common LUT Format (CLF) - A Common File Format for Look-Up 

35 Tables. Retrieved June 24, 2020, from http://j.mp/S-2014-006 

36""" 

37 

38from __future__ import annotations 

39 

40import typing 

41 

42import numpy as np 

43 

44from colour.algebra import sdiv, sdiv_mode 

45 

46if typing.TYPE_CHECKING: 

47 from colour.hints import ( 

48 ArrayLike, 

49 Literal, 

50 NDArrayFloat, 

51 ) 

52 

53from colour.hints import cast 

54from colour.utilities import ( 

55 as_float, 

56 as_float_array, 

57 optional, 

58 validate_method, 

59 zeros, 

60) 

61 

62__author__ = "Colour Developers" 

63__copyright__ = "Copyright 2013 Colour Developers" 

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

65__maintainer__ = "Colour Developers" 

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

67__status__ = "Production" 

68 

69__all__ = [ 

70 "logarithmic_function_basic", 

71 "logarithmic_function_quasilog", 

72 "logarithmic_function_camera", 

73 "log_encoding_Log2", 

74 "log_decoding_Log2", 

75] 

76 

77FLT_MIN = 1.175494e-38 

78 

79 

80def logarithmic_function_basic( 

81 x: ArrayLike, 

82 style: ( 

83 Literal["log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"] | str 

84 ) = "log2", 

85 base: int = 2, 

86) -> NDArrayFloat: 

87 """ 

88 Apply a logarithmic or anti-logarithmic transformation to the specified array. 

89 

90 Parameters 

91 ---------- 

92 x 

93 Logarithmically encoded data :math:`x`. 

94 style 

95 Specifies the behaviour for the logarithmic function to operate: 

96 

97 - *log10*: Applies a base 10 logarithm to the passed value. 

98 - *antiLog10*: Applies a base 10 anti-logarithm to the passed value. 

99 - *log2*: Applies a base 2 logarithm to the passed value. 

100 - *antiLog2*: Applies a base 2 anti-logarithm to the passed value. 

101 - *logB*: Applies an arbitrary base logarithm to the passed value. 

102 - *antiLogB*: Applies an arbitrary base anti-logarithm to the passed 

103 value. 

104 base 

105 Logarithmic base used for the conversion. 

106 

107 Returns 

108 ------- 

109 :class:`numpy.ndarray` 

110 Logarithmically transformed data. 

111 

112 Examples 

113 -------- 

114 The basic logarithmic function *styles* operate as follows: 

115 

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

117 -2.4739311... 

118 >>> logarithmic_function_basic(0.18, "log10") # doctest: +ELLIPSIS 

119 -0.7447274... 

120 >>> logarithmic_function_basic(0.18, "logB", 3) # doctest: +ELLIPSIS 

121 -1.5608767... 

122 >>> logarithmic_function_basic( # doctest: +ELLIPSIS 

123 ... -2.473931188332412, "antiLog2" 

124 ... ) 

125 0.18000000... 

126 >>> logarithmic_function_basic( # doctest: +ELLIPSIS 

127 ... -0.7447274948966939, "antiLog10" 

128 ... ) 

129 0.18000000... 

130 >>> logarithmic_function_basic( # doctest: +ELLIPSIS 

131 ... -1.5608767950073117, "antiLogB", 3 

132 ... ) 

133 0.18000000... 

134 """ 

135 

136 x = as_float_array(x) 

137 style = validate_method( 

138 style, 

139 ("log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"), 

140 '"{0}" style is invalid, it must be one of {1}!', 

141 ) 

142 

143 if style == "log10": 

144 return as_float(np.where(x >= FLT_MIN, np.log10(x), np.log10(FLT_MIN))) 

145 

146 if style == "antilog10": 

147 return as_float(10**x) 

148 

149 if style == "log2": 

150 return as_float(np.where(x >= FLT_MIN, np.log2(x), np.log2(FLT_MIN))) 

151 

152 if style == "antilog2": 

153 return as_float(2**x) 

154 

155 if style == "logb": 

156 return as_float(np.log(x) / np.log(base)) 

157 

158 # style == 'antilogb' 

159 return as_float(base**x) 

160 

161 

162def logarithmic_function_quasilog( 

163 x: ArrayLike, 

164 style: Literal["linToLog", "logToLin"] | str = "linToLog", 

165 base: int = 2, 

166 log_side_slope: float = 1, 

167 lin_side_slope: float = 1, 

168 log_side_offset: float = 0, 

169 lin_side_offset: float = 0, 

170) -> NDArrayFloat: 

171 """ 

172 Apply the *Quasilog* logarithmic function for encoding and decoding. 

173 

174 This function implements a logarithmic transformation with configurable 

175 slopes and offsets for both linear and logarithmic sides. 

176 

177 Parameters 

178 ---------- 

179 x 

180 Logarithmically encoded data :math:`x`. 

181 style 

182 Specifies the behaviour for the logarithmic function to operate: 

183 

184 - *linToLog*: Apply a logarithm to convert linear data to 

185 logarithmic data. 

186 - *logToLin*: Apply an anti-logarithm to convert logarithmic 

187 data to linear data. 

188 base 

189 Logarithmic base used for the conversion. 

190 log_side_slope 

191 Slope (or gain) applied to the log side of the logarithmic function. 

192 The default value is 1. 

193 lin_side_slope 

194 Slope of the linear side of the logarithmic function. The default 

195 value is 1. 

196 log_side_offset 

197 Offset applied to the log side of the logarithmic function. The 

198 default value is 0. 

199 lin_side_offset 

200 Offset applied to the linear side of the logarithmic function. The 

201 default value is 0. 

202 

203 Returns 

204 ------- 

205 :class:`numpy.ndarray` 

206 Encoded/Decoded data. 

207 

208 Examples 

209 -------- 

210 >>> logarithmic_function_quasilog(0.18, "linToLog") # doctest: +ELLIPSIS 

211 -2.4739311... 

212 >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS 

213 ... -2.473931188332412, "logToLin" 

214 ... ) 

215 0.18000000... 

216 """ 

217 

218 x = as_float_array(x) 

219 style = validate_method( 

220 style, 

221 ("lintolog", "logtolin"), 

222 '"{0}" style is invalid, it must be one of {1}!', 

223 ) 

224 

225 if style == "lintolog": 

226 y = ( 

227 log_side_slope 

228 * ( 

229 np.log(np.maximum(lin_side_slope * x + lin_side_offset, FLT_MIN)) 

230 / np.log(base) 

231 ) 

232 + log_side_offset 

233 ) 

234 else: # style == 'logtolin' 

235 with sdiv_mode(): 

236 y = sdiv( 

237 base ** sdiv(x - log_side_offset, log_side_slope) - lin_side_offset, 

238 lin_side_slope, 

239 ) 

240 

241 return as_float(y) 

242 

243 

244def logarithmic_function_camera( 

245 x: ArrayLike, 

246 style: (Literal["cameraLinToLog", "cameraLogToLin"] | str) = "cameraLinToLog", 

247 base: int = 2, 

248 log_side_slope: float = 1, 

249 lin_side_slope: float = 1, 

250 log_side_offset: float = 0, 

251 lin_side_offset: float = 0, 

252 lin_side_break: float = 0.005, 

253 linear_slope: float | None = None, 

254) -> NDArrayFloat: 

255 """ 

256 Apply a camera logarithmic function to the specified array. 

257 

258 Apply a piece-wise function with logarithmic and linear segments 

259 for encoding or decoding camera data. 

260 

261 Parameters 

262 ---------- 

263 x 

264 Logarithmically encoded data :math:`x`. 

265 style 

266 Specifies the behaviour for the logarithmic function to operate: 

267 

268 - *cameraLinToLog*: Applies a piece-wise function with logarithmic 

269 and linear segments on linear values, converting them to non-linear 

270 values. 

271 - *cameraLogToLin*: Applies a piece-wise function with logarithmic 

272 and linear segments on non-linear values, converting them to linear 

273 values. 

274 base 

275 Logarithmic base used for the conversion. 

276 log_side_slope 

277 Slope (or gain) applied to the log side of the logarithmic function. 

278 The default value is 1. 

279 lin_side_slope 

280 Slope of the linear side of the logarithmic function. The 

281 default value is 1. 

282 log_side_offset 

283 Offset applied to the log side of the logarithmic function. The 

284 default value is 0. 

285 lin_side_offset 

286 Offset applied to the linear side of the logarithmic function. 

287 The default value is 0. 

288 lin_side_break 

289 Break-point, defined in linear space, at which the piece-wise 

290 function transitions between the logarithmic and linear 

291 segments. 

292 linear_slope 

293 Slope of the linear portion of the curve. The default value is 

294 *None*. 

295 

296 Returns 

297 ------- 

298 :class:`numpy.ndarray` 

299 Encoded/Decoded data. 

300 

301 Examples 

302 -------- 

303 >>> logarithmic_function_camera( # doctest: +ELLIPSIS 

304 ... 0.18, "cameraLinToLog" 

305 ... ) 

306 -2.4739311... 

307 >>> logarithmic_function_camera( # doctest: +ELLIPSIS 

308 ... -2.4739311883324122, "cameraLogToLin" 

309 ... ) 

310 0.1800000... 

311 """ 

312 

313 x = as_float_array(x) 

314 style = validate_method( 

315 style, 

316 ("cameraLinToLog", "cameraLogToLin"), 

317 '"{0}" style is invalid, it must be one of {1}!', 

318 ) 

319 

320 log_side_break = ( 

321 log_side_slope 

322 * (np.log(lin_side_slope * lin_side_break + lin_side_offset) / np.log(base)) 

323 + log_side_offset 

324 ) 

325 

326 with sdiv_mode(): 

327 linear_slope = cast( 

328 "float", 

329 optional( 

330 linear_slope, 

331 ( 

332 log_side_slope 

333 * ( 

334 sdiv( 

335 lin_side_slope, 

336 (lin_side_slope * lin_side_break + lin_side_offset) 

337 * np.log(base), 

338 ) 

339 ) 

340 ), 

341 ), 

342 ) 

343 

344 linear_offset = log_side_break - linear_slope * lin_side_break 

345 

346 y = zeros(x.shape) 

347 if style == "cameralintolog": 

348 m_x = x <= lin_side_break 

349 y[m_x] = linear_slope * x[m_x] + linear_offset 

350 y[~m_x] = logarithmic_function_quasilog( 

351 x[~m_x], 

352 "linToLog", 

353 base, 

354 log_side_slope, 

355 lin_side_slope, 

356 log_side_offset, 

357 lin_side_offset, 

358 ) 

359 else: # style == 'cameralogtolin' 

360 with sdiv_mode(): 

361 m_x = x <= log_side_break 

362 y[m_x] = sdiv(x[m_x] - linear_offset, linear_slope) 

363 y[~m_x] = logarithmic_function_quasilog( 

364 x[~m_x], 

365 "logToLin", 

366 base, 

367 log_side_slope, 

368 lin_side_slope, 

369 log_side_offset, 

370 lin_side_offset, 

371 ) 

372 

373 return as_float(y) 

374 

375 

376def log_encoding_Log2( 

377 lin: ArrayLike, 

378 middle_grey: float = 0.18, 

379 min_exposure: float = -6.5, 

380 max_exposure: float = 6.5, 

381) -> NDArrayFloat: 

382 """ 

383 Apply the common *Log2* log encoding opto-electronic transfer function (OETF). 

384 

385 Parameters 

386 ---------- 

387 lin 

388 Linear *Log2* decoded data. 

389 middle_grey 

390 *Middle Grey* exposure value. 

391 min_exposure 

392 Minimum exposure level. 

393 max_exposure 

394 Maximum exposure level. 

395 

396 Returns 

397 ------- 

398 :class:`numpy.ndarray` 

399 Non-linear *Log2* encoded data. 

400 

401 Notes 

402 ----- 

403 +--------------+-----------------------+---------------+ 

404 | **Domain** | **Scale - Reference** | **Scale - 1** | 

405 +==============+=======================+===============+ 

406 | ``lin`` | 1 | 1 | 

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

408 

409 +--------------+-----------------------+---------------+ 

410 | **Range** | **Scale - Reference** | **Scale - 1** | 

411 +==============+=======================+===============+ 

412 | ``log_norm`` | 1 | 1 | 

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

414 

415 - The common *Log2* encoding function can be used to build linear to 

416 logarithmic shapers in the *ACES OCIO configuration*. 

417 - A (48-nits OCIO) shaper having values in a linear domain, can be 

418 encoded to a logarithmic domain: 

419 

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

421 | **Shaper Domain** | **Shaper Range** | 

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

423 | [0.002, 16.291] | [0, 1] | 

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

425 

426 References 

427 ---------- 

428 :cite:`TheAcademyofMotionPictureArtsandSciencesa` 

429 

430 Examples 

431 -------- 

432 >>> log_encoding_Log2(0.18) 

433 0.5 

434 """ 

435 

436 lin = as_float_array(lin) 

437 

438 lg2 = np.log2(lin / middle_grey) 

439 log_norm = (lg2 - min_exposure) / (max_exposure - min_exposure) 

440 

441 return as_float(log_norm) 

442 

443 

444def log_decoding_Log2( 

445 log_norm: ArrayLike, 

446 middle_grey: float = 0.18, 

447 min_exposure: float = -6.5, 

448 max_exposure: float = 6.5, 

449) -> NDArrayFloat: 

450 """ 

451 Apply the common *Log2* log decoding inverse opto-electronic transfer 

452 function (OETF). 

453 

454 Parameters 

455 ---------- 

456 log_norm 

457 Non-linear *Log2* encoded data. 

458 middle_grey 

459 *Middle Grey* exposure value. 

460 min_exposure 

461 Minimum exposure level. 

462 max_exposure 

463 Maximum exposure level. 

464 

465 Returns 

466 ------- 

467 :class:`numpy.ndarray` 

468 Linear *Log2* decoded data. 

469 

470 Notes 

471 ----- 

472 +--------------+-----------------------+---------------+ 

473 | **Domain** | **Scale - Reference** | **Scale - 1** | 

474 +==============+=======================+===============+ 

475 | ``log_norm`` | 1 | 1 | 

476 +--------------+-----------------------+---------------+ 

477 

478 +--------------+-----------------------+---------------+ 

479 | **Range** | **Scale - Reference** | **Scale - 1** | 

480 +==============+=======================+===============+ 

481 | ``lin`` | 1 | 1 | 

482 +--------------+-----------------------+---------------+ 

483 

484 - The common *Log2* decoding function can be used to build logarithmic 

485 to linear shapers in the *ACES OCIO configuration*. 

486 - The shaper with logarithmic encoded values can be decoded back to 

487 linear domain: 

488 

489 +-------------------+-------------------+ 

490 | **Shaper Range** | **Shaper Domain** | 

491 +===================+===================+ 

492 | [0, 1] | [0.002, 16.291] | 

493 +-------------------+-------------------+ 

494 

495 References 

496 ---------- 

497 :cite:`TheAcademyofMotionPictureArtsandSciencesb` 

498 

499 Examples 

500 -------- 

501 >>> log_decoding_Log2(0.5) # doctest: +ELLIPSIS 

502 0.1799999... 

503 """ 

504 

505 log_norm = as_float_array(log_norm) 

506 

507 lg2 = log_norm * (max_exposure - min_exposure) + min_exposure 

508 lin = (2**lg2) * middle_grey 

509 

510 return as_float(lin)