Coverage for tests / test_pretty_picture_maker.py: 18%

342 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-21 10:40 +0000

1import pytest 

2import numpy as np 

3from lsst.pipe.tasks.prettyPictureMaker._functors._bounds_remapper import BoundsRemapper 

4from lsst.pipe.tasks.prettyPictureMaker._functors._lum_scale import LumCompressor 

5from lsst.pipe.tasks.prettyPictureMaker._functors._local_contrast import ( 

6 DiffusionFunction, 

7 LocalContrastEnhancer, 

8) 

9from lsst.pipe.tasks.prettyPictureMaker._functors._color_scale import ColorScaler 

10from lsst.pipe.tasks.prettyPictureMaker._functors._exposure_fusion import ExposureBracketer 

11from lsst.pipe.tasks.prettyPictureMaker._functors._gamut_fixer import GamutFixer 

12from lsst.pipe.tasks.prettyPictureMaker._utils import FeatheredMosaicCreator 

13from lsst.pipe.tasks.prettyPictureMaker._colorMapper import lsstRGB 

14from lsst.geom import Box2I 

15 

16 

17class TestColorScaler: 

18 def test_basic_saturation(self): 

19 """Verify saturation scaling preserves hue while adjusting chroma.""" 

20 scaler = ColorScaler(saturation=0.6) 

21 lum_old = np.ones((50, 50)) * 0.5 

22 lum_new = np.ones((50, 50)) * 0.7 

23 np.random.seed(42) 

24 a = np.random.uniform(-0.3, 0.3, (50, 50)) 

25 b = np.random.uniform(-0.3, 0.3, (50, 50)) 

26 

27 new_a, new_b = scaler(lum_old, lum_new, a, b) 

28 

29 original_hue = np.arctan2(b, a) 

30 new_hue = np.arctan2(new_b, new_a) 

31 np.testing.assert_allclose(original_hue, new_hue, rtol=1e-5) 

32 

33 original_chroma = np.sqrt(a**2 + b**2) 

34 new_chroma = np.sqrt(new_a**2 + new_b**2) 

35 assert np.all(new_chroma < original_chroma) 

36 

37 def test_saturation_factor(self): 

38 """Verify different saturation values produce different chromaticity.""" 

39 scaler_low_sat = ColorScaler(saturation=0.3) 

40 scaler_high_sat = ColorScaler(saturation=0.9) 

41 

42 lum_old = np.ones((50, 50)) * 0.5 

43 lum_new = np.ones((50, 50)) * 0.7 

44 np.random.seed(42) 

45 a = np.random.uniform(-0.3, 0.3, (50, 50)) 

46 b = np.random.uniform(-0.3, 0.3, (50, 50)) 

47 

48 new_a_low, _ = scaler_low_sat(lum_old, lum_new, a, b) 

49 new_a_high, _ = scaler_high_sat(lum_old, lum_new, a, b) 

50 

51 assert not np.allclose(new_a_low, new_a_high) 

52 

53 def test_max_chroma_clipping(self): 

54 """Verify chromaticity is clipped to maxChroma.""" 

55 scaler = ColorScaler(saturation=1.0, maxChroma=0.1) 

56 

57 lum_old = np.ones((50, 50)) * 0.5 

58 lum_new = np.ones((50, 50)) * 0.7 

59 np.random.seed(42) 

60 a = np.random.uniform(-0.4, 0.4, (50, 50)) 

61 b = np.random.uniform(-0.4, 0.4, (50, 50)) 

62 

63 new_a, new_b = scaler(lum_old, lum_new, a, b) 

64 new_chroma = np.sqrt(new_a**2 + new_b**2) 

65 

66 assert np.all(new_chroma <= 0.1 + 1e-6) 

67 

68 def test_zero_chroma(self): 

69 """Verify achromatic input remains achromatic.""" 

70 scaler = ColorScaler(saturation=0.6) 

71 

72 lum_old = np.ones((50, 50)) * 0.5 

73 lum_new = np.ones((50, 50)) * 0.7 

74 a = np.zeros((50, 50)) 

75 b = np.zeros((50, 50)) 

76 

77 new_a, new_b = scaler(lum_old, lum_new, a, b) 

78 

79 np.testing.assert_array_equal(new_a, 0.0) 

80 np.testing.assert_array_equal(new_b, 0.0) 

81 

82 def test_equalizer_levels(self): 

83 """Verify equalizer_levels modifies chromaticity.""" 

84 scaler_no_eq = ColorScaler(saturation=0.6, equalizer_levels=None) 

85 scaler_with_eq = ColorScaler(saturation=0.6, equalizer_levels=[1.1, 0.9]) 

86 

87 lum_old = np.ones((50, 50)) * 0.5 

88 lum_new = np.ones((50, 50)) * 0.7 

89 np.random.seed(42) 

90 a = np.random.uniform(-0.3, 0.3, (50, 50)) 

91 b = np.random.uniform(-0.3, 0.3, (50, 50)) 

92 

93 new_a_no_eq, new_b_no_eq = scaler_no_eq(lum_old, lum_new, a, b) 

94 new_a_with_eq, new_b_with_eq = scaler_with_eq(lum_old, lum_new, a, b) 

95 

96 assert not np.allclose((new_a_no_eq, new_b_no_eq), (new_a_with_eq, new_b_with_eq)) 

97 

98 

99class TestBoundsRemapper: 

100 def test_absmax_scaling(self): 

101 """Verify scaling using the fixed absMax value.""" 

102 remapper = BoundsRemapper(absMax=100.0, quant=1.0) 

103 img = np.ones((10, 10, 3)) * 50.0 

104 expected = np.ones((10, 10, 3)) * 0.5 

105 result = remapper(img) 

106 np.testing.assert_allclose(result, expected) 

107 

108 def test_quant_scaling(self): 

109 """Verify scaling using the quantile-based approach (absMax=None).""" 

110 remapper = BoundsRemapper(absMax=None, quant=0.5) 

111 data = np.linspace(0, 10, 100).reshape(10, 10) 

112 img = np.stack([data, data, data], axis=-1) 

113 # 95th percentile of linspace(0, 10, 100) is 9.5 

114 # scale = 9.5 * 0.5 = 4.75 

115 expected = np.clip(img / 4.75, 0, 1) 

116 result = remapper(img) 

117 np.testing.assert_allclose(result, expected) 

118 

119 def test_clipping(self): 

120 """Verify that values exceeding the scale are clipped to 1.0.""" 

121 remapper = BoundsRemapper(absMax=10.0) 

122 img = np.array([[[0.0, 0.0, 0.0], [5.0, 5.0, 5.0], [15.0, 15.0, 15.0]]], dtype=float) 

123 expected = np.array([[[0.0, 0.0, 0.0], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0]]], dtype=float) 

124 result = remapper(img) 

125 np.testing.assert_allclose(result, expected) 

126 

127 def test_short_circuit(self): 

128 """Verify that if max is already 1, the image is returned as-is.""" 

129 remapper = BoundsRemapper(absMax=100.0) 

130 img = np.ones((5, 5, 3)) 

131 result = remapper(img) 

132 np.testing.assert_array_equal(result, img) 

133 

134 

135class TestLumCompressor: 

136 def test_basic_stretching(self): 

137 """Verify that asinh stretching maps values into [0, 1].""" 

138 remapper = LumCompressor(stretch=400.0, max=1.0) 

139 # Input values ranging from 0 to 1000 

140 img = np.linspace(0, 1000, 100).reshape(10, 10) 

141 result = remapper(img) 

142 

143 assert np.all(result >= 0.0) 

144 assert np.all(result <= 1.0) 

145 assert result.shape == img.shape 

146 

147 def test_contrast_params(self): 

148 """Verify that highlight and shadow parameters affect the output.""" 

149 # Test shadow parameter: increasing shadow should shift values 

150 remapper_low_shadow = LumCompressor(shadow=0.0, highlight=1.0) 

151 remapper_high_shadow = LumCompressor(shadow=0.5, highlight=1.0) 

152 

153 img = np.linspace(0.1, 0.9, 100).reshape(10, 10) 

154 res_low = remapper_low_shadow(img) 

155 res_high = remapper_high_shadow(img) 

156 

157 # High shadow should result in a different distribution 

158 assert not np.allclose(res_low, res_high) 

159 

160 def test_midtone_adjustment(self): 

161 """Verify that midtone parameter shifts the intensity pivot.""" 

162 remapper_mid_05 = LumCompressor(midtone=0.5) 

163 remapper_mid_08 = LumCompressor(midtone=0.8) 

164 

165 img = np.linspace(0.1, 0.9, 100).reshape(10, 10) 

166 res_05 = remapper_mid_05(img) 

167 res_08 = remapper_mid_08(img) 

168 

169 assert not np.allclose(res_05, res_08) 

170 

171 def test_clipping(self): 

172 """Verify that the final output is strictly clipped to [0, 1].""" 

173 # Using extreme parameters to force values out of bounds 

174 remapper = LumCompressor(stretch=1000.0, highlight=0.1, shadow=0.9) 

175 img = np.linspace(0, 100, 100).reshape(10, 10) 

176 result = remapper(img) 

177 

178 assert np.all(result >= 0.0) 

179 assert np.all(result <= 1.0) 

180 

181 

182class TestDiffusionFunction: 

183 def test_basic(self): 

184 """Verify that diffusion produces a different output from input.""" 

185 diffuser = DiffusionFunction() 

186 img = np.random.rand(50, 50) 

187 result = diffuser(img) 

188 assert not np.allclose(result, img) 

189 

190 def test_high_iterations(self): 

191 """Verify that high iterations produce more diffused output.""" 

192 diffuser_low = DiffusionFunction(iterations=1) 

193 diffuser_high = DiffusionFunction(iterations=10) 

194 img = np.random.rand(50, 50) 

195 res_low = diffuser_low(img) 

196 res_high = diffuser_high(img) 

197 assert not np.allclose(res_low, res_high) 

198 

199 

200class TestLocalContrastEnhancer: 

201 def test_basic_enhancement(self): 

202 """Verify that local contrast enhancement increases contrast.""" 

203 enhancer = LocalContrastEnhancer(doDiffusion=False) 

204 img = np.ones((50, 50)) * 0.5 + np.random.rand(50, 50) * 0.01 

205 result = enhancer(img) 

206 assert result.std() > img.std() 

207 

208 def test_diffusion_off(self): 

209 """Verify that turning off diffusion produces different results.""" 

210 enhancer_with_diffusion = LocalContrastEnhancer(doDiffusion=True) 

211 enhancer_without_diffusion = LocalContrastEnhancer(doDiffusion=False) 

212 img = np.random.rand(50, 50) 

213 res_with = enhancer_with_diffusion(img) 

214 res_without = enhancer_without_diffusion(img) 

215 assert not np.allclose(res_with, res_without) 

216 

217 

218class TestFeatheredMosaicCreator: 

219 def test_make_featherings_basic(self): 

220 """Verify featherings are created with correct shapes.""" 

221 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

222 creator._make_featherings((50, 50)) 

223 

224 assert creator.featherings is not None 

225 assert len(creator.featherings) == 4 

226 

227 for feather in creator.featherings: 

228 assert feather.shape == (50, 50) 

229 

230 def test_make_featherings_symmetry(self): 

231 """Verify top/bottom and left/right masks are symmetric.""" 

232 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

233 creator._make_featherings((50, 50)) 

234 

235 top, bottom, left, right = creator.featherings 

236 

237 # Check symmetry excluding the first/last rows/columns where 1e-17 is placed 

238 np.testing.assert_allclose(top[1:30, :], bottom[20:49, :][::-1, :], rtol=1e-5) 

239 np.testing.assert_allclose(left[:, 1:30], right[:, 20:49][:, ::-1], rtol=1e-5) 

240 

241 def test_make_featherings_values(self): 

242 """Verify ramp values are correct.""" 

243 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

244 creator._make_featherings((50, 50)) 

245 

246 top, bottom, left, right = creator.featherings 

247 

248 assert top[0, 0] < 1e-6 

249 assert np.allclose(top[20, 0], 1.0, atol=0.1) 

250 

251 assert np.allclose(bottom[30, 0], 1.0, atol=0.1) 

252 assert bottom[-1, 0] < 1e-6 

253 

254 assert np.allclose(left[0, 20], 1.0, atol=0.1) 

255 assert right[0, -1] < 1e-6 

256 

257 def test_make_featherings_bin_factor(self): 

258 """Verify bin_factor reduces resolution.""" 

259 creator_no_bin = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

260 creator_bin = FeatheredMosaicCreator(patch_grow=10, bin_factor=2) 

261 

262 creator_no_bin._make_featherings((50, 50)) 

263 creator_bin._make_featherings((50, 50)) 

264 

265 assert creator_bin.featherings[0].shape == creator_no_bin.featherings[0].shape 

266 

267 def test_add_to_image_full_overlap(self): 

268 """Verify no feathering when box == newBox.""" 

269 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

270 image = np.zeros((50, 50, 3)) 

271 patch = np.ones((50, 50, 3)) * 0.5 

272 

273 box = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

274 newBox = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

275 

276 creator.add_to_image(image, patch, newBox, box, reverse=False) 

277 

278 np.testing.assert_allclose(image, 0.5, atol=0.1) 

279 

280 def test_add_to_image_single_edge(self): 

281 """Verify only the differing edge gets feathering.""" 

282 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

283 image = np.zeros((60, 60, 3)) 

284 patch = np.ones((50, 50, 3)) * 0.5 

285 

286 box = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

287 newBox = Box2I(Box2I.Point(0, 5), Box2I.Extent(50, 55)) 

288 

289 creator.add_to_image(image, patch, newBox, box, reverse=False) 

290 

291 assert image.shape == (60, 60, 3) 

292 

293 def test_add_to_image_multi_edge(self): 

294 """Verify multiple edges get combined feathering.""" 

295 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

296 image = np.zeros((70, 70, 3)) 

297 patch = np.ones((50, 50, 3)) * 0.5 

298 

299 box = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

300 newBox = Box2I(Box2I.Point(5, 5), Box2I.Extent(55, 55)) 

301 

302 creator.add_to_image(image, patch, newBox, box, reverse=False) 

303 

304 assert image.shape == (70, 70, 3) 

305 

306 def test_add_to_image_rgb(self): 

307 """Verify RGB (3D) images are handled correctly.""" 

308 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

309 image = np.zeros((50, 50, 3)) 

310 patch = np.ones((50, 50, 3)) * 0.5 

311 

312 box = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

313 newBox = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

314 

315 creator.add_to_image(image, patch, newBox, box, reverse=False) 

316 

317 assert image.shape == (50, 50, 3) 

318 np.testing.assert_allclose(image[0, 0, :], 0.5, atol=0.1) 

319 

320 def test_add_to_image_reverse(self): 

321 """Verify reverse flips the patch.""" 

322 creator = FeatheredMosaicCreator(patch_grow=10, bin_factor=1) 

323 image = np.zeros((50, 50, 3)) 

324 patch = np.ones((50, 50, 3)) 

325 

326 box = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

327 newBox = Box2I(Box2I.Point(0, 0), Box2I.Extent(50, 50)) 

328 

329 creator.add_to_image(image, patch, newBox, box, reverse=True) 

330 

331 assert image.shape == (50, 50, 3) 

332 

333 

334class TestExposureBracketer: 

335 def test_basic_fusion(self): 

336 """Verify exposure bracketing fusion produces a balanced result.""" 

337 bracketer = ExposureBracketer() 

338 img = np.random.rand(50, 50) * 0.5 

339 result = bracketer(img) 

340 

341 assert result.shape == img.shape 

342 assert np.all(result >= 0.0) 

343 assert np.all(result <= 1.0) 

344 

345 def test_single_bracket(self): 

346 """Verify single bracket returns scaled image without fusion.""" 

347 bracketer = ExposureBracketer(exposureBrackets=[1.25]) 

348 img = np.random.rand(50, 50) * 0.5 

349 result = bracketer(img) 

350 expected = img / 1.25 

351 

352 np.testing.assert_allclose(result, expected) 

353 

354 def test_no_brackets(self): 

355 """Verify None brackets returns input unchanged.""" 

356 bracketer = ExposureBracketer(exposureBrackets=None) 

357 img = np.random.rand(50, 50) 

358 result = bracketer(img) 

359 

360 np.testing.assert_array_equal(result, img) 

361 

362 def test_different_brackets(self): 

363 """Verify different bracket configurations produce different outputs.""" 

364 bracketer_default = ExposureBracketer() 

365 bracketer_custom = ExposureBracketer(exposureBrackets=[1.5, 1, 0.5]) 

366 

367 img = np.random.rand(50, 50) * 0.5 

368 res_default = bracketer_default(img) 

369 res_custom = bracketer_custom(img) 

370 

371 assert not np.allclose(res_default, res_custom) 

372 

373 def test_clipping_behavior(self): 

374 """Verify values > 1.0 receive reduced weighting during fusion.""" 

375 bracketer = ExposureBracketer() 

376 img = np.random.rand(50, 50) * 0.5 

377 img[25, 25] = 1.5 

378 

379 result = bracketer(img) 

380 

381 assert result.shape == img.shape 

382 assert np.all(result >= 0.0) 

383 

384 

385class TestGamutFixer: 

386 def test_gamut_fixer_none_method(self): 

387 """Verify 'none' method returns original RGB conversion.""" 

388 fixer = GamutFixer(gamutMethod="none") 

389 Lab = np.random.rand(50, 50, 3) * 0.4 - 0.2 

390 Lab[:, :, 0] = Lab[:, :, 0] * 0.8 + 0.1 

391 Lab[25, 25, :] = [0.5, 0.5, 0.5] 

392 xyz_whitepoint = (0.31272, 0.32903) 

393 

394 result = fixer(Lab, xyz_whitepoint) 

395 

396 assert result.shape == Lab.shape[:-1] + (3,) 

397 

398 def test_gamut_fixer_inpaint_method(self): 

399 """Verify 'inpaint' method fixes small out-of-gamut regions.""" 

400 fixer = GamutFixer(gamutMethod="inpaint", max_size=10000) 

401 Lab = np.random.rand(50, 50, 3) * 0.4 - 0.2 

402 Lab[:, :, 0] = Lab[:, :, 0] * 0.8 + 0.1 

403 xyz_whitepoint = (0.31272, 0.32903) 

404 

405 result = fixer(Lab, xyz_whitepoint) 

406 

407 assert result.shape == Lab.shape[:-1] + (3,) 

408 assert np.all(result <= 1.0) 

409 

410 def test_gamut_fixer_mapping_method(self): 

411 """Verify 'mapping' method remaps out-of-gamut colors.""" 

412 fixer = GamutFixer(gamutMethod="mapping") 

413 Lab = np.random.rand(50, 50, 3) * 0.4 - 0.2 

414 Lab[:, :, 0] = Lab[:, :, 0] * 0.8 + 0.1 

415 Lab[25, 25, :] = [0.8, 0.8, 0.8] 

416 xyz_whitepoint = (0.31272, 0.32903) 

417 

418 result = fixer(Lab, xyz_whitepoint) 

419 

420 assert result.shape == Lab.shape[:-1] + (3,) 

421 assert np.all(result <= 1.0) 

422 

423 def test_gamut_fixer_heal_method(self): 

424 """Verify 'heal' method heals out-of-gamut regions.""" 

425 fixer = GamutFixer(gamutMethod="heal", max_size=1000) 

426 Lab = np.random.rand(50, 50, 3) * 0.4 - 0.2 

427 Lab[:, :, 0] = Lab[:, :, 0] * 0.8 + 0.1 

428 xyz_whitepoint = (0.31272, 0.32903) 

429 

430 result = fixer(Lab, xyz_whitepoint) 

431 

432 assert result.shape == Lab.shape[:-1] + (3,) 

433 assert np.all(result <= 1.0) 

434 

435 def test_gamut_fixer_no_out_of_bounds(self): 

436 fixer = GamutFixer(gamutMethod="inpaint") 

437 Lab = np.random.rand(50, 50, 3) * 0.4 - 0.2 

438 Lab[:, :, 0] = Lab[:, :, 0] * 0.8 + 0.1 

439 xyz_whitepoint = (0.31272, 0.32903) 

440 

441 result = fixer(Lab, xyz_whitepoint) 

442 

443 assert result.shape == Lab.shape[:-1] + (3,) 

444 assert np.all(result >= 0.0) 

445 assert np.all(result <= 1.0) 

446 

447 

448class TestColorMapper: 

449 def test_basic_integration(self): 

450 """Verify lsstRGB produces valid RGB output with defaults.""" 

451 np.random.seed(42) 

452 r = np.random.rand(100, 100) * 0.99 + 0.01 

453 g = np.random.rand(100, 100) * 0.99 + 0.01 

454 b = np.random.rand(100, 100) * 0.99 + 0.01 

455 

456 result = lsstRGB(r, g, b) 

457 

458 assert result.shape == (100, 100, 3) 

459 assert result.dtype == np.float64 

460 assert np.all(result >= 0.0) 

461 assert np.all(result <= 1.0) 

462 

463 def test_shape_validation(self): 

464 """Verify ValueError is raised for mismatched shapes.""" 

465 np.random.seed(42) 

466 r = np.random.rand(100, 100) 

467 g = np.random.rand(100, 99) 

468 b = np.random.rand(100, 100) 

469 

470 with pytest.raises(ValueError): 

471 lsstRGB(r, g, b) 

472 

473 def test_none_functors(self): 

474 """Verify all functors set to None returns remapped RGB.""" 

475 np.random.seed(42) 

476 r = np.random.rand(100, 100) * 0.99 + 0.01 

477 g = np.random.rand(100, 100) * 0.99 + 0.01 

478 b = np.random.rand(100, 100) * 0.99 + 0.01 

479 

480 result = lsstRGB( 

481 r, 

482 g, 

483 b, 

484 local_contrast=None, 

485 scale_lum=None, 

486 scale_color=None, 

487 bracketing_function=None, 

488 gamut_remapping_function=None, 

489 remap_bounds=None, 

490 cieWhitePoint=(0.31272, 0.32903), 

491 ) 

492 

493 assert result.shape == (100, 100, 3) 

494 np.testing.assert_allclose(result[..., 0], r, rtol=1e-5) 

495 np.testing.assert_allclose(result[..., 1], g, rtol=1e-5) 

496 np.testing.assert_allclose(result[..., 2], b, rtol=1e-5) 

497 

498 def test_psf_deconvolution(self): 

499 """Verify PSF deconvolution produces different output.""" 

500 np.random.seed(42) 

501 r = np.random.rand(100, 100) * 0.99 + 0.01 

502 g = np.random.rand(100, 100) * 0.99 + 0.01 

503 b = np.random.rand(100, 100) * 0.99 + 0.01 

504 

505 x = np.linspace(-2, 2, 5) 

506 xx, yy = np.meshgrid(x, x) 

507 psf = np.exp(-(xx**2 + yy**2)) 

508 psf /= psf.sum() 

509 

510 result_no_psf = lsstRGB(r, g, b, psf=None) 

511 result_with_psf = lsstRGB(r, g, b, psf=psf) 

512 

513 assert result_no_psf.shape == result_with_psf.shape 

514 assert not np.allclose(result_no_psf, result_with_psf) 

515 

516 def test_cie_white_point(self): 

517 """Verify different white points produce different outputs.""" 

518 np.random.seed(42) 

519 r = np.random.rand(100, 100) * 0.99 + 0.01 

520 g = np.random.rand(100, 100) * 0.99 + 0.01 

521 b = np.random.rand(100, 100) * 0.99 + 0.01 

522 

523 result_default = lsstRGB(r, g, b, cieWhitePoint=(0.28, 0.28)) 

524 result_d65 = lsstRGB(r, g, b, cieWhitePoint=(0.31272, 0.32903)) 

525 

526 assert not np.allclose(result_default, result_d65)