Coverage for io/luts/tests/test_sequence.py: 100%

88 statements  

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

1"""Define the unit tests for the :mod:`colour.io.luts.sequence` module.""" 

2 

3from __future__ import annotations 

4 

5import textwrap 

6import typing 

7 

8import numpy as np 

9 

10from colour.constants import TOLERANCE_ABSOLUTE_TESTS 

11 

12if typing.TYPE_CHECKING: 

13 from colour.hints import Any, ArrayLike, NDArrayFloat 

14 

15from colour.io.luts import ( 

16 LUT1D, 

17 LUT3D, 

18 AbstractLUTSequenceOperator, 

19 LUT3x1D, 

20 LUTSequence, 

21) 

22from colour.models import gamma_function 

23from colour.utilities import as_float_array, tstack 

24 

25__author__ = "Colour Developers" 

26__copyright__ = "Copyright 2013 Colour Developers" 

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

28__maintainer__ = "Colour Developers" 

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

30__status__ = "Production" 

31 

32__all__ = [ 

33 "TestLUTSequence", 

34] 

35 

36 

37class TestLUTSequence: 

38 """ 

39 Define :class:`colour.io.luts.sequence.LUTSequence` class unit tests 

40 methods. 

41 """ 

42 

43 def setup_method(self) -> None: 

44 """Initialise the common tests attributes.""" 

45 

46 self._LUT_1 = LUT1D(LUT1D.linear_table(16) + 0.125, "Nemo 1D") 

47 self._LUT_2 = LUT3D(LUT3D.linear_table(16) ** (1 / 2.2), "Nemo 3D") 

48 self._LUT_3 = LUT3x1D(LUT3x1D.linear_table(16) * 0.750, "Nemo 3x1D") 

49 self._LUT_sequence = LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3) 

50 

51 samples = np.linspace(0, 1, 5) 

52 

53 self._RGB = tstack([samples, samples, samples]) 

54 

55 def test_required_attributes(self) -> None: 

56 """Test the presence of required attributes.""" 

57 

58 required_attributes = ("sequence",) 

59 

60 for attribute in required_attributes: 

61 assert attribute in dir(LUTSequence) 

62 

63 def test_required_methods(self) -> None: 

64 """Test the presence of required methods.""" 

65 

66 required_methods = ( 

67 "__init__", 

68 "__getitem__", 

69 "__setitem__", 

70 "__delitem__", 

71 "__len__", 

72 "__str__", 

73 "__repr__", 

74 "__eq__", 

75 "__ne__", 

76 "insert", 

77 "apply", 

78 "copy", 

79 ) 

80 

81 for method in required_methods: 

82 assert method in dir(LUTSequence) 

83 

84 def test_sequence(self) -> None: 

85 """Test :class:`colour.io.luts.sequence.LUTSequence.sequence` property.""" 

86 

87 sequence = [self._LUT_1, self._LUT_2, self._LUT_3] 

88 LUT_sequence = LUTSequence() 

89 LUT_sequence.sequence = sequence 

90 assert self._LUT_sequence.sequence == sequence 

91 

92 def test__init__(self) -> None: 

93 """Test :class:`colour.io.luts.sequence.LUTSequence.__init__` method.""" 

94 

95 assert LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3) == self._LUT_sequence 

96 

97 def test__getitem__(self) -> None: 

98 """Test :class:`colour.io.luts.sequence.LUTSequence.__getitem__` method.""" 

99 

100 assert self._LUT_sequence[0] == self._LUT_1 

101 assert self._LUT_sequence[1] == self._LUT_2 

102 assert self._LUT_sequence[2] == self._LUT_3 

103 

104 def test__setitem__(self) -> None: 

105 """Test :class:`colour.io.luts.sequence.LUTSequence.__setitem__` method.""" 

106 

107 LUT_sequence = self._LUT_sequence.copy() 

108 LUT_sequence[0] = self._LUT_3 

109 LUT_sequence[1] = self._LUT_1 

110 LUT_sequence[2] = self._LUT_2 

111 

112 assert LUT_sequence[1] == self._LUT_1 

113 assert LUT_sequence[2] == self._LUT_2 

114 assert LUT_sequence[0] == self._LUT_3 

115 

116 def test__delitem__(self) -> None: 

117 """Test :class:`colour.io.luts.sequence.LUTSequence.__delitem__` method.""" 

118 

119 LUT_sequence = self._LUT_sequence.copy() 

120 

121 del LUT_sequence[0] 

122 del LUT_sequence[0] 

123 

124 assert LUT_sequence[0] == self._LUT_3 

125 

126 def test__len__(self) -> None: 

127 """Test :class:`colour.io.luts.sequence.LUTSequence.__len__` method.""" 

128 

129 assert len(self._LUT_sequence) == 3 

130 

131 def test__str__(self) -> None: 

132 """Test :class:`colour.io.luts.sequence.LUTSequence.__str__` method.""" 

133 

134 assert str(self._LUT_sequence) == ( 

135 textwrap.dedent( 

136 """ 

137 LUT Sequence 

138 ------------ 

139 

140 Overview 

141 

142 LUT1D --> LUT3D --> LUT3x1D 

143 

144 Operations 

145 

146 LUT1D - Nemo 1D 

147 --------------- 

148 

149 Dimensions : 1 

150 Domain : [ 0. 1.] 

151 Size : (16,) 

152 

153 LUT3D - Nemo 3D 

154 --------------- 

155 

156 Dimensions : 3 

157 Domain : [[ 0. 0. 0.] 

158 [ 1. 1. 1.]] 

159 Size : (16, 16, 16, 3) 

160 

161 LUT3x1D - Nemo 3x1D 

162 ------------------- 

163 

164 Dimensions : 2 

165 Domain : [[ 0. 0. 0.] 

166 [ 1. 1. 1.]] 

167 Size : (16, 3) 

168 """ 

169 ).strip() 

170 ) 

171 

172 def test__repr__(self) -> None: 

173 """Test :class:`colour.io.luts.sequence.LUTSequence.__repr__` method.""" 

174 

175 LUT_sequence = self._LUT_sequence.copy() 

176 LUT_sequence[1].table = LUT3D.linear_table(5) 

177 

178 assert repr(LUT_sequence) == ( 

179 textwrap.dedent( 

180 """ 

181LUTSequence( 

182 LUT1D([ 0.125 , 0.19166667, 0.25833333, 0.325 , 0.39166667, 

183 0.45833333, 0.525 , 0.59166667, 0.65833333, 0.725 , 

184 0.79166667, 0.85833333, 0.925 , 0.99166667, 1.05833333, 

185 1.125 ], 

186 'Nemo 1D', 

187 [ 0., 1.], 

188 16), 

189 LUT3D([[[[ 0. , 0. , 0. ], 

190 [ 0. , 0. , 0.25], 

191 [ 0. , 0. , 0.5 ], 

192 [ 0. , 0. , 0.75], 

193 [ 0. , 0. , 1. ]], 

194 

195 [[ 0. , 0.25, 0. ], 

196 [ 0. , 0.25, 0.25], 

197 [ 0. , 0.25, 0.5 ], 

198 [ 0. , 0.25, 0.75], 

199 [ 0. , 0.25, 1. ]], 

200 

201 [[ 0. , 0.5 , 0. ], 

202 [ 0. , 0.5 , 0.25], 

203 [ 0. , 0.5 , 0.5 ], 

204 [ 0. , 0.5 , 0.75], 

205 [ 0. , 0.5 , 1. ]], 

206 

207 [[ 0. , 0.75, 0. ], 

208 [ 0. , 0.75, 0.25], 

209 [ 0. , 0.75, 0.5 ], 

210 [ 0. , 0.75, 0.75], 

211 [ 0. , 0.75, 1. ]], 

212 

213 [[ 0. , 1. , 0. ], 

214 [ 0. , 1. , 0.25], 

215 [ 0. , 1. , 0.5 ], 

216 [ 0. , 1. , 0.75], 

217 [ 0. , 1. , 1. ]]], 

218 

219 [[[ 0.25, 0. , 0. ], 

220 [ 0.25, 0. , 0.25], 

221 [ 0.25, 0. , 0.5 ], 

222 [ 0.25, 0. , 0.75], 

223 [ 0.25, 0. , 1. ]], 

224 

225 [[ 0.25, 0.25, 0. ], 

226 [ 0.25, 0.25, 0.25], 

227 [ 0.25, 0.25, 0.5 ], 

228 [ 0.25, 0.25, 0.75], 

229 [ 0.25, 0.25, 1. ]], 

230 

231 [[ 0.25, 0.5 , 0. ], 

232 [ 0.25, 0.5 , 0.25], 

233 [ 0.25, 0.5 , 0.5 ], 

234 [ 0.25, 0.5 , 0.75], 

235 [ 0.25, 0.5 , 1. ]], 

236 

237 [[ 0.25, 0.75, 0. ], 

238 [ 0.25, 0.75, 0.25], 

239 [ 0.25, 0.75, 0.5 ], 

240 [ 0.25, 0.75, 0.75], 

241 [ 0.25, 0.75, 1. ]], 

242 

243 [[ 0.25, 1. , 0. ], 

244 [ 0.25, 1. , 0.25], 

245 [ 0.25, 1. , 0.5 ], 

246 [ 0.25, 1. , 0.75], 

247 [ 0.25, 1. , 1. ]]], 

248 

249 [[[ 0.5 , 0. , 0. ], 

250 [ 0.5 , 0. , 0.25], 

251 [ 0.5 , 0. , 0.5 ], 

252 [ 0.5 , 0. , 0.75], 

253 [ 0.5 , 0. , 1. ]], 

254 

255 [[ 0.5 , 0.25, 0. ], 

256 [ 0.5 , 0.25, 0.25], 

257 [ 0.5 , 0.25, 0.5 ], 

258 [ 0.5 , 0.25, 0.75], 

259 [ 0.5 , 0.25, 1. ]], 

260 

261 [[ 0.5 , 0.5 , 0. ], 

262 [ 0.5 , 0.5 , 0.25], 

263 [ 0.5 , 0.5 , 0.5 ], 

264 [ 0.5 , 0.5 , 0.75], 

265 [ 0.5 , 0.5 , 1. ]], 

266 

267 [[ 0.5 , 0.75, 0. ], 

268 [ 0.5 , 0.75, 0.25], 

269 [ 0.5 , 0.75, 0.5 ], 

270 [ 0.5 , 0.75, 0.75], 

271 [ 0.5 , 0.75, 1. ]], 

272 

273 [[ 0.5 , 1. , 0. ], 

274 [ 0.5 , 1. , 0.25], 

275 [ 0.5 , 1. , 0.5 ], 

276 [ 0.5 , 1. , 0.75], 

277 [ 0.5 , 1. , 1. ]]], 

278 

279 [[[ 0.75, 0. , 0. ], 

280 [ 0.75, 0. , 0.25], 

281 [ 0.75, 0. , 0.5 ], 

282 [ 0.75, 0. , 0.75], 

283 [ 0.75, 0. , 1. ]], 

284 

285 [[ 0.75, 0.25, 0. ], 

286 [ 0.75, 0.25, 0.25], 

287 [ 0.75, 0.25, 0.5 ], 

288 [ 0.75, 0.25, 0.75], 

289 [ 0.75, 0.25, 1. ]], 

290 

291 [[ 0.75, 0.5 , 0. ], 

292 [ 0.75, 0.5 , 0.25], 

293 [ 0.75, 0.5 , 0.5 ], 

294 [ 0.75, 0.5 , 0.75], 

295 [ 0.75, 0.5 , 1. ]], 

296 

297 [[ 0.75, 0.75, 0. ], 

298 [ 0.75, 0.75, 0.25], 

299 [ 0.75, 0.75, 0.5 ], 

300 [ 0.75, 0.75, 0.75], 

301 [ 0.75, 0.75, 1. ]], 

302 

303 [[ 0.75, 1. , 0. ], 

304 [ 0.75, 1. , 0.25], 

305 [ 0.75, 1. , 0.5 ], 

306 [ 0.75, 1. , 0.75], 

307 [ 0.75, 1. , 1. ]]], 

308 

309 [[[ 1. , 0. , 0. ], 

310 [ 1. , 0. , 0.25], 

311 [ 1. , 0. , 0.5 ], 

312 [ 1. , 0. , 0.75], 

313 [ 1. , 0. , 1. ]], 

314 

315 [[ 1. , 0.25, 0. ], 

316 [ 1. , 0.25, 0.25], 

317 [ 1. , 0.25, 0.5 ], 

318 [ 1. , 0.25, 0.75], 

319 [ 1. , 0.25, 1. ]], 

320 

321 [[ 1. , 0.5 , 0. ], 

322 [ 1. , 0.5 , 0.25], 

323 [ 1. , 0.5 , 0.5 ], 

324 [ 1. , 0.5 , 0.75], 

325 [ 1. , 0.5 , 1. ]], 

326 

327 [[ 1. , 0.75, 0. ], 

328 [ 1. , 0.75, 0.25], 

329 [ 1. , 0.75, 0.5 ], 

330 [ 1. , 0.75, 0.75], 

331 [ 1. , 0.75, 1. ]], 

332 

333 [[ 1. , 1. , 0. ], 

334 [ 1. , 1. , 0.25], 

335 [ 1. , 1. , 0.5 ], 

336 [ 1. , 1. , 0.75], 

337 [ 1. , 1. , 1. ]]]], 

338 'Nemo 3D', 

339 [[ 0., 0., 0.], 

340 [ 1., 1., 1.]], 

341 5), 

342 LUT3x1D([[ 0. , 0. , 0. ], 

343 [ 0.05, 0.05, 0.05], 

344 [ 0.1 , 0.1 , 0.1 ], 

345 [ 0.15, 0.15, 0.15], 

346 [ 0.2 , 0.2 , 0.2 ], 

347 [ 0.25, 0.25, 0.25], 

348 [ 0.3 , 0.3 , 0.3 ], 

349 [ 0.35, 0.35, 0.35], 

350 [ 0.4 , 0.4 , 0.4 ], 

351 [ 0.45, 0.45, 0.45], 

352 [ 0.5 , 0.5 , 0.5 ], 

353 [ 0.55, 0.55, 0.55], 

354 [ 0.6 , 0.6 , 0.6 ], 

355 [ 0.65, 0.65, 0.65], 

356 [ 0.7 , 0.7 , 0.7 ], 

357 [ 0.75, 0.75, 0.75]], 

358 'Nemo 3x1D', 

359 [[ 0., 0., 0.], 

360 [ 1., 1., 1.]], 

361 16) 

362) 

363""".strip() 

364 ) 

365 ) 

366 

367 def test__eq__(self) -> None: 

368 """Test :class:`colour.io.luts.sequence.LUTSequence.__eq__` method.""" 

369 

370 LUT_sequence_1 = LUTSequence(self._LUT_1, self._LUT_2, self._LUT_3) 

371 LUT_sequence_2 = LUTSequence(self._LUT_1, self._LUT_2) 

372 

373 assert self._LUT_sequence == LUT_sequence_1 

374 

375 assert self._LUT_sequence != self._LUT_sequence[0] 

376 

377 assert LUT_sequence_1 != LUT_sequence_2 

378 

379 def test__neq__(self) -> None: 

380 """Test :class:`colour.io.luts.sequence.LUTSequence.__neq__` method.""" 

381 

382 assert self._LUT_sequence != LUTSequence( 

383 self._LUT_1, self._LUT_2.copy() * 0.75, self._LUT_3 

384 ) 

385 

386 def test_insert(self) -> None: 

387 """Test :class:`colour.io.luts.sequence.LUTSequence.insert` method.""" 

388 

389 LUT_sequence = self._LUT_sequence.copy() 

390 

391 LUT_sequence.insert(1, self._LUT_2.copy()) 

392 

393 assert LUT_sequence == LUTSequence( 

394 self._LUT_1, 

395 self._LUT_2, 

396 self._LUT_2, 

397 self._LUT_3, 

398 ) 

399 

400 def test_apply(self) -> None: 

401 """Test :class:`colour.io.luts.sequence.LUTSequence.apply` method.""" 

402 

403 class GammaOperator(AbstractLUTSequenceOperator): 

404 """ 

405 Gamma operator for unit tests. 

406 

407 Parameters 

408 ---------- 

409 gamma 

410 Gamma value. 

411 """ 

412 

413 def __init__(self, gamma: ArrayLike = 1) -> None: 

414 self._gamma = as_float_array(gamma) 

415 

416 def apply( 

417 self, 

418 RGB: ArrayLike, 

419 *args: Any, # noqa: ARG002 

420 **kwargs: Any, 

421 ) -> NDArrayFloat: 

422 """ 

423 Apply the *LUT* sequence operator to the specified *RGB* colourspace 

424 array. 

425 

426 Parameters 

427 ---------- 

428 RGB 

429 *RGB* colourspace array to apply the *LUT* sequence 

430 operator onto. 

431 

432 Returns 

433 ------- 

434 :class:`numpy.ndarray` 

435 Processed *RGB* colourspace array. 

436 """ 

437 

438 direction = kwargs.get("direction", "Forward") 

439 

440 gamma = self._gamma if direction == "Forward" else 1.0 / self._gamma 

441 

442 return as_float_array(gamma_function(RGB, gamma)) 

443 

444 LUT_sequence = self._LUT_sequence.copy() 

445 LUT_sequence.insert(1, GammaOperator(1 / 2.2)) 

446 samples = np.linspace(0, 1, 5) 

447 RGB = tstack([samples, samples, samples]) 

448 

449 np.testing.assert_allclose( 

450 LUT_sequence.apply(RGB, GammaOperator={"direction": "Inverse"}), 

451 np.array( 

452 [ 

453 [0.03386629, 0.03386629, 0.03386629], 

454 [0.27852298, 0.27852298, 0.27852298], 

455 [0.46830881, 0.46830881, 0.46830881], 

456 [0.65615595, 0.65615595, 0.65615595], 

457 [0.75000000, 0.75000000, 0.75000000], 

458 ] 

459 ), 

460 atol=TOLERANCE_ABSOLUTE_TESTS, 

461 )