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

1"""

2 Common Log Encodings

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

4

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

13 References

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/\

21 518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\

22 ACESutil.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/\

29 518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\

30 ACESutil.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

38 from __future__ import annotations

39

40 import typing

41

42 import numpy as np

43

44 from colour.algebra import sdiv, sdiv_mode

45

46 if typing.TYPE_CHECKING:

47 from colour.hints import (

48 ArrayLike,

49 Literal,

50 NDArrayFloat,

51 )

52

53 from colour.hints import cast

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

77 FLT_MIN = 1.175494e-38

78

79

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

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

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

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

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