Coverage for io/luts/__init__.py: 34%

1"""

2 Look-Up Table (LUT) I/O

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

4

5 Implement support for reading and writing industry-standard Look-Up Table

6(LUT) formats used in colour pipelines and digital intermediate workflows.

7

8- :class:`colour.LUT1D`: 1D LUT for single-channel transformations

9- :class:`colour.LUT3x1D`: Three separate 1D LUTs for per-channel operations

10- :class:`colour.LUT3D`: 3D LUT for complex colour transformations

11- :class:`colour.LUTSequence`: Sequential LUT operations

12- :class:`colour.LUTOperatorMatrix`: Matrix-based LUT operations

13- :func:`colour.io.read_LUT`: Auto-detect and read LUT files

14- :func:`colour.io.write_LUT`: Write LUT files in specified formats

15

16 References

17----------

18- :cite:`AdobeSystems2013b` : Adobe Systems. (2013). Cube LUT Specification.

19 https://drive.google.com/open?id=143Eh08ZYncCAMwJ1q4gWxVOqR_OSWYvs

20- :cite:`Chamberlain2015` : Chamberlain, P. (2015). LUT documentation (to

21 create from another program). Retrieved August 23, 2018, from

22 https://forum.blackmagicdesign.com/viewtopic.php?f=21&t=40284#p232952

23- :cite:`RisingSunResearch` : Rising Sun Research. (n.d.). cineSpace LUT

24 Library. Retrieved November 30, 2018, from

25 https://sourceforge.net/projects/cinespacelutlib/

26"""

27

28 from __future__ import annotations

29

30 import os

31 import typing

32

33 if typing.TYPE_CHECKING:

34 from colour.hints import Any, LiteralLUTReadMethod, LiteralLUTWriteMethod, PathLike

35

36 from colour.utilities import (

37 CanonicalMapping,

38 filter_kwargs,

39 validate_method,

40)

41

42# isort: split

43

44 from .lut import LUT1D, LUT3D, LUT3x1D, LUT_to_LUT

45 from .operator import AbstractLUTSequenceOperator, LUTOperatorMatrix

46 from .sequence import LUTSequence

47

48# isort: split

49

50 from .cinespace_csp import read_LUT_Cinespace, write_LUT_Cinespace

51 from .iridas_cube import read_LUT_IridasCube, write_LUT_IridasCube

52 from .resolve_cube import read_LUT_ResolveCube, write_LUT_ResolveCube

53 from .sony_spi1d import read_LUT_SonySPI1D, write_LUT_SonySPI1D

54 from .sony_spi3d import read_LUT_SonySPI3D, write_LUT_SonySPI3D

55 from .sony_spimtx import read_LUT_SonySPImtx, write_LUT_SonySPImtx

56

57 __all__ = [

58 "LUT1D",

59 "LUT3D",

60 "LUT3x1D",

61 "LUT_to_LUT",

62]

63 __all__ += [

64 "AbstractLUTSequenceOperator",

65 "LUTOperatorMatrix",

66]

67 __all__ += [

68 "LUTSequence",

69]

70 __all__ += [

71 "read_LUT_Cinespace",

72 "write_LUT_Cinespace",

73]

74 __all__ += [

75 "read_LUT_IridasCube",

76 "write_LUT_IridasCube",

77]

78 __all__ += [

79 "read_LUT_ResolveCube",

80 "write_LUT_ResolveCube",

81]

82 __all__ += [

83 "read_LUT_SonySPI1D",

84 "write_LUT_SonySPI1D",

85]

86 __all__ += [

87 "read_LUT_SonySPI3D",

88 "write_LUT_SonySPI3D",

89]

90 __all__ += [

91 "read_LUT_SonySPImtx",

92 "write_LUT_SonySPImtx",

93]

94

95 MAPPING_EXTENSION_TO_LUT_FORMAT: CanonicalMapping = CanonicalMapping(

96 {

97 ".cube": "Iridas Cube",

98 ".spi1d": "Sony SPI1D",

99 ".spi3d": "Sony SPI3D",

100 ".spimtx": "Sony SPImtx",

101 ".csp": "Cinespace",

102 }

103)

104"""Extension to *LUT* format."""

105

106 LUT_READ_METHODS: CanonicalMapping = CanonicalMapping(

107 {

108 "Cinespace": read_LUT_Cinespace,

109 "Iridas Cube": read_LUT_IridasCube,

110 "Resolve Cube": read_LUT_ResolveCube,

111 "Sony SPI1D": read_LUT_SonySPI1D,

112 "Sony SPI3D": read_LUT_SonySPI3D,

113 "Sony SPImtx": read_LUT_SonySPImtx,

114 }

115)

116 LUT_READ_METHODS.__doc__ = """

117 Supported *LUT* reading methods.

118

119 References

120----------

121:cite:`AdobeSystems2013b`, :cite:`Chamberlain2015`

122"""

123

124

125 def read_LUT(

126 path: str | PathLike,

127 method: LiteralLUTReadMethod | str | None = None,

128 **kwargs: Any,

129) -> LUT1D | LUT3x1D | LUT3D | LUTSequence | LUTOperatorMatrix:

130 """

131 Read the specified *LUT* file.

132

133 Parameters

134 ----------

135 path

136 *LUT* path.

137 method

138 Reading method, if *None*, the method will be auto-detected

139 according to extension.

140

141 Returns

142 -------

143 :class:`colour.LUT1D` or :class:`colour.LUT3x1D` or \

144:class:`colour.LUT3D` or :class:`colour.LUTSequence` or \

145:class:`colour.LUTOperatorMatrix`

146 :class:`colour.LUT1D` or :class:`colour.LUT3x1D` or

147 :class:`colour.LUT3D` or :class:`colour.LUTSequence` or

148 :class:`colour.LUTOperatorMatrix` class instance.

149

150 Raises

151 ------

152 ValueError

153 If the *LUT* file format is not supported or if reading fails.

154

155 References

156 ----------

157 :cite:`AdobeSystems2013b`, :cite:`Chamberlain2015`,

158 :cite:`RisingSunResearch`

159

160 Examples

161 --------

162 Reading a 3x1D *Iridas* *.cube* *LUT*:

163

164 >>> path = os.path.join(

165 ... os.path.dirname(__file__),

166 ... "tests",

167 ... "resources",

168 ... "iridas_cube",

169 ... "ACES_Proxy_10_to_ACES.cube",

170 ... )

171 >>> print(read_LUT(path))

172 LUT3x1D - ACES Proxy 10 to ACES

173 -------------------------------

174 <BLANKLINE>

175 Dimensions : 2

176 Domain : [[ 0. 0. 0.]

177 [ 1. 1. 1.]]

178 Size : (32, 3)

179

180 Reading a 1D *Sony* *.spi1d* *LUT*:

181

182 >>> path = os.path.join(

183 ... os.path.dirname(__file__),

184 ... "tests",

185 ... "resources",

186 ... "sony_spi1d",

187 ... "eotf_sRGB_1D.spi1d",

188 ... )

189 >>> print(read_LUT(path))

190 LUT1D - eotf sRGB 1D

191 --------------------

192 <BLANKLINE>

193 Dimensions : 1

194 Domain : [-0.1 1.5]

195 Size : (16,)

196 Comment 01 : Generated by "Colour 0.3.11".

197 Comment 02 : "colour.models.eotf_sRGB".

198

199 Reading a 3D *Sony* *.spi3d* *LUT*:

200

201 >>> path = os.path.join(

202 ... os.path.dirname(__file__),

203 ... "tests",

204 ... "resources",

205 ... "sony_spi3d",

206 ... "Colour_Correct.spi3d",

207 ... )

208 >>> print(read_LUT(path))

209 LUT3D - Colour Correct

210 ----------------------

211 <BLANKLINE>

212 Dimensions : 3

213 Domain : [[ 0. 0. 0.]

214 [ 1. 1. 1.]]

215 Size : (4, 4, 4, 3)

216 Comment 01 : Adapted from a LUT generated by Foundry::LUT.

217

218 Reading a *Sony* *.spimtx* *LUT*:

219

220 >>> path = os.path.join(

221 ... os.path.dirname(__file__),

222 ... "tests",

223 ... "resources",

224 ... "sony_spimtx",

225 ... "dt.spimtx",

226 ... )

227 >>> print(read_LUT(path))

228 LUTOperatorMatrix - dt

229 ----------------------

230 <BLANKLINE>

231 Matrix : [[ 0.864274 0. 0. 0. ]

232 [ 0. 0.864274 0. 0. ]

233 [ 0. 0. 0.864274 0. ]

234 [ 0. 0. 0. 1. ]]

235 Offset : [ 0. 0. 0. 0.]

236 """

237

238 path = str(path)

239

240 method = (

241 MAPPING_EXTENSION_TO_LUT_FORMAT[os.path.splitext(path)[-1]].lower()

242 if method is None

243 else validate_method(method, tuple(LUT_WRITE_METHODS))

244 )

245

246 function = LUT_READ_METHODS[method]

247

248 try:

249 return function(path, **filter_kwargs(function, **kwargs))

250 except ValueError as error:

251 # Case where a "Resolve Cube" with "LUT3x1D" shaper was read as an

252 # "Iridas Cube" "LUT".

253 if method == "iridas cube":

254 function = LUT_READ_METHODS["Resolve Cube"]

255 return function(path, **filter_kwargs(function, **kwargs))

256

257 raise ValueError from error

258

259

260 LUT_WRITE_METHODS = CanonicalMapping(

261 {

262 "Cinespace": write_LUT_Cinespace,

263 "Iridas Cube": write_LUT_IridasCube,

264 "Resolve Cube": write_LUT_ResolveCube,

265 "Sony SPI1D": write_LUT_SonySPI1D,

266 "Sony SPI3D": write_LUT_SonySPI3D,

267 "Sony SPImtx": write_LUT_SonySPImtx,

268 }

269)

270 LUT_WRITE_METHODS.__doc__ = """

271 Supported *LUT* writing methods.

272

273 References

274----------

275:cite:`AdobeSystems2013b`, :cite:`Chamberlain2015`

276"""

277

278

279 def write_LUT(

280 LUT: LUT1D | LUT3x1D | LUT3D | LUTSequence | LUTOperatorMatrix,

281 path: str | PathLike,

282 decimals: int = 7,

283 method: LiteralLUTWriteMethod | str | None = None,

284 **kwargs: Any,

285) -> bool:

286 """

287 Write the specified *LUT* to the specified file.

288

289 Parameters

290 ----------

291 LUT

292 :class:`colour.LUT1D` or :class:`colour.LUT3x1D` or

293 :class:`colour.LUT3D` or :class:`colour.LUTSequence` or

294 :class:`colour.LUTOperatorMatrix` class instance to write at the

295 specified path.

296 path

297 *LUT* file path.

298 decimals

299 Number of decimal places for formatting numeric values.

300 method

301 Writing method, if *None*, the method will be auto-detected

302 according to the file extension.

303

304 Returns

305 -------

306 :class:`bool`

307 Definition success.

308

309 References

310 ----------

311 :cite:`AdobeSystems2013b`, :cite:`Chamberlain2015`,

312 :cite:`RisingSunResearch`

313

314 Examples

315 --------

316 Writing a 3x1D *Iridas* *.cube* *LUT*:

317

318 >>> import numpy as np

319 >>> from colour.algebra import spow

320 >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])

321 >>> LUT = LUT3x1D(

322 ... spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),

323 ... "My LUT",

324 ... domain,

325 ... comments=["A first comment.", "A second comment."],

326 ... )

327 >>> write_LUT(LUT, "My_LUT.cube") # doctest: +SKIP

328

329 Writing a 1D *Sony* *.spi1d* *LUT*:

330

331 >>> domain = np.array([-0.1, 1.5])

332 >>> LUT = LUT1D(

333 ... spow(LUT1D.linear_table(16, domain), 1 / 2.2),

334 ... "My LUT",

335 ... domain,

336 ... comments=["A first comment.", "A second comment."],

337 ... )

338 >>> write_LUT(LUT, "My_LUT.spi1d") # doctest: +SKIP

339

340 Writing a 3D *Sony* *.spi3d* *LUT*:

341

342 >>> LUT = LUT3D(

343 ... LUT3D.linear_table(16) ** (1 / 2.2),

344 ... "My LUT",

345 ... np.array([[0, 0, 0], [1, 1, 1]]),

346 ... comments=["A first comment.", "A second comment."],

347 ... )

348 >>> write_LUT(LUT, "My_LUT.cube") # doctest: +SKIP

349 """

350

351 path = str(path)

352

353 method = (

354 MAPPING_EXTENSION_TO_LUT_FORMAT[os.path.splitext(path)[-1]].lower()

355 if method is None

356 else validate_method(method, tuple(LUT_WRITE_METHODS))

357 )

358

359 if method == "iridas cube" and isinstance(LUT, LUTSequence):

360 method = "resolve cube"

361

362 function = LUT_WRITE_METHODS[method]

363

364 return function(LUT, path, decimals, **filter_kwargs(function, **kwargs))

365

366

367 __all__ += [

368 "LUT_READ_METHODS",

369 "read_LUT",

370 "LUT_WRITE_METHODS",

371 "write_LUT",

372]