Coverage for blindness/machado2009.py: 74%

1"""

2 Simulation of CVD - Machado, Oliveira and Fernandes (2009)

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

4

5 Define the *Machado et al. (2009)* physiologically-based model for

6 simulation of colour vision deficiency.

7

8- :func:`colour.msds_cmfs_anomalous_trichromacy_Machado2009`

9- :func:`colour.matrix_anomalous_trichromacy_Machado2009`

10- :func:`colour.matrix_cvd_Machado2009`

11

12 References

13----------

14- :cite:`Colblindora` : Colblindor. (n.d.). Deuteranopia - Red-Green Color

15 Blindness. Retrieved July 4, 2015, from

16 http://www.color-blindness.com/deuteranopia-red-green-color-blindness/

17- :cite:`Colblindorb` : Colblindor. (n.d.). Protanopia - Red-Green Color

18 Blindness. Retrieved July 4, 2015, from

19 http://www.color-blindness.com/protanopia-red-green-color-blindness/

20- :cite:`Colblindorc` : Colblindor. (n.d.). Tritanopia - Blue-Yellow Color

21 Blindness. Retrieved July 4, 2015, from

22 http://www.color-blindness.com/tritanopia-blue-yellow-color-blindness/

23- :cite:`Machado2009` : Machado, G.M., Oliveira, M. M., & Fernandes, L.

24 (2009). A Physiologically-based Model for Simulation of Color Vision

25 Deficiency. IEEE Transactions on Visualization and Computer Graphics,

26 15(6), 1291-1298. doi:10.1109/TVCG.2009.113

27"""

28

29 from __future__ import annotations

30

31 import typing

32

33 import numpy as np

34

35 from colour.algebra import vecmul

36 from colour.blindness import CVD_MATRICES_MACHADO2010

37

38 if typing.TYPE_CHECKING:

39 from colour.characterisation import RGB_DisplayPrimaries

40

41 from colour.colorimetry import LMS_ConeFundamentals, SpectralShape, reshape_msds

42

43 if typing.TYPE_CHECKING:

44 from colour.hints import ArrayLike, Literal, NDArrayFloat

45

46 from colour.utilities import (

47 as_float_array,

48 as_int_scalar,

49 tsplit,

50 tstack,

51 usage_warning,

52)

53

54 __author__ = "Colour Developers"

55 __copyright__ = "Copyright 2013 Colour Developers"

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

57 __maintainer__ = "Colour Developers"

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

59 __status__ = "Production"

60

61 __all__ = [

62 "MATRIX_LMS_TO_WSYBRG",

63 "matrix_RGB_to_WSYBRG",

64 "msds_cmfs_anomalous_trichromacy_Machado2009",

65 "matrix_anomalous_trichromacy_Machado2009",

66 "matrix_cvd_Machado2009",

67]

68

69 MATRIX_LMS_TO_WSYBRG: NDArrayFloat = np.array(

70 [

71 [0.600, 0.400, 0.000],

72 [0.240, 0.105, -0.700],

73 [1.200, -1.600, 0.400],

74 ]

75)

76"""

77 Ingling and Tsou (1977) matrix converting from cones responses to

78 opponent-colour space.

79"""

80

81

82 def matrix_RGB_to_WSYBRG(

83 cmfs: LMS_ConeFundamentals, primaries: RGB_DisplayPrimaries

84) -> NDArrayFloat:

85 """

86 Compute the matrix for transforming from *RGB* colourspace to

87 opponent-colour space using *Machado et al. (2009)* method.

88

89 Parameters

90 ----------

91 cmfs

92 *LMS* cone fundamentals colour matching functions.

93 primaries

94 *RGB* display primaries tri-spectral distributions.

95

96 Returns

97 -------

98 :class:`numpy.ndarray`

99 Matrix transforming from *RGB* colourspace to opponent-colour

100 space.

101

102 Examples

103 --------

104 >>> from colour.characterisation import MSDS_DISPLAY_PRIMARIES

105 >>> from colour.colorimetry import MSDS_CMFS_LMS

106 >>> cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 2 Degree Cone Fundamentals"]

107 >>> d_LMS = np.array([15, 0, 0])

108 >>> primaries = MSDS_DISPLAY_PRIMARIES["Apple Studio Display"]

109 >>> matrix_RGB_to_WSYBRG(cmfs, primaries) # doctest: +ELLIPSIS

110 array([[ 0.2126535..., 0.6704626..., 0.1168838...],

111 [ 4.7095295..., 12.4862869..., -16.1958165...],

112 [-11.1518474..., 15.2534789..., -3.1016315...]])

113 """

114

115 wavelengths = cmfs.wavelengths

116 WSYBRG = vecmul(MATRIX_LMS_TO_WSYBRG, cmfs.values)

117 WS, YB, RG = tsplit(WSYBRG)

118

119 primaries = reshape_msds(

120 primaries,

121 cmfs.shape,

122 copy=False,

123 extrapolator_kwargs={"method": "Constant", "left": 0, "right": 0},

124 )

125

126 R, G, B = tsplit(primaries.values)

127

128 WS_R = np.trapezoid(R * WS, wavelengths)

129 WS_G = np.trapezoid(G * WS, wavelengths)

130 WS_B = np.trapezoid(B * WS, wavelengths)

131

132 YB_R = np.trapezoid(R * YB, wavelengths)

133 YB_G = np.trapezoid(G * YB, wavelengths)

134 YB_B = np.trapezoid(B * YB, wavelengths)

135

136 RG_R = np.trapezoid(R * RG, wavelengths)

137 RG_G = np.trapezoid(G * RG, wavelengths)

138 RG_B = np.trapezoid(B * RG, wavelengths)

139

140 M_G = as_float_array(

141 [

142 [WS_R, WS_G, WS_B],

143 [YB_R, YB_G, YB_B],

144 [RG_R, RG_G, RG_B],

145 ]

146 )

147

148 M_G /= np.sum(M_G, axis=-1)[:, None]

149

150 return M_G

151

152

153 def msds_cmfs_anomalous_trichromacy_Machado2009(

154 cmfs: LMS_ConeFundamentals, d_LMS: ArrayLike

155) -> LMS_ConeFundamentals:

156 """

157 Shift the specified *LMS* cone fundamentals colour matching functions

158 with the specified :math:`\\Delta_{LMS}` shift amount in nanometers to

159 simulate anomalous trichromacy using *Machado et al. (2009)* method.

160

161 Parameters

162 ----------

163 cmfs

164 *LMS* cone fundamentals colour matching functions.

165 d_LMS

166 :math:`\\Delta_{LMS}` wavelength shift amount in nanometers for each

167 cone type.

168

169 Notes

170 -----

171 - Input *LMS* cone fundamentals colour matching functions interval is

172 expected to be 1 nanometer, incompatible input will be interpolated

173 at 1 nanometer interval.

174 - Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20].

175

176 Returns

177 -------

178 :class:`colour.LMS_ConeFundamentals`

179 Anomalous trichromacy *LMS* cone fundamentals colour matching

180 functions.

181

182 Warnings

183 --------

184 *Machado et al. (2009)* simulation of tritanomaly is based on the shift

185 paradigm as an approximation to the actual phenomenon and restrain the

186 model from trying to model tritanopia.

187 The pre-generated matrices are using a shift value in domain [5, 59]

188 contrary to the domain [0, 20] used for protanomaly and deuteranomaly

189 simulation.

190

191 References

192 ----------

193 :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,

194 :cite:`Machado2009`

195

196 Examples

197 --------

198 >>> from colour.colorimetry import MSDS_CMFS_LMS

199 >>> cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 2 Degree Cone Fundamentals"]

200 >>> cmfs[450]

201 array([ 0.0498639, 0.0870524, 0.955393 ])

202 >>> msds_cmfs_anomalous_trichromacy_Machado2009(cmfs, np.array([15, 0, 0]))[

203 ... 450

204 ... ] # doctest: +ELLIPSIS

205 array([ 0.0891288..., 0.0870524 , 0.955393 ])

206 """

207

208 cmfs = cmfs.copy()

209

210 if cmfs.shape.interval != 1:

211 cmfs.interpolate(SpectralShape(cmfs.shape.start, cmfs.shape.end, 1))

212

213 cmfs.extrapolator_kwargs = {"method": "Constant", "left": 0, "right": 0}

214

215 L, M, _S = tsplit(cmfs.values)

216 d_L, d_M, d_S = tsplit(d_LMS)

217

218 if d_S != 0:

219 usage_warning(

220 '"Machado et al. (2009)" simulation of tritanomaly is based on '

221 "the shift paradigm as an approximation to the actual phenomenon "

222 "and restrain the model from trying to model tritanopia.\n"

223 "The pre-generated matrices are using a shift value in domain "

224 "[5, 59] contrary to the domain [0, 20] used for protanomaly and "

225 "deuteranomaly simulation."

226 )

227

228 area_L = np.trapezoid(L, cmfs.wavelengths)

229 area_M = np.trapezoid(M, cmfs.wavelengths)

230

231 def alpha(x: NDArrayFloat) -> NDArrayFloat:

232 """Compute :math:`alpha` factor."""

233

234 return (20 - x) / 20

235

236 # Corrected equations as per:

237 # http://www.inf.ufrgs.br/~oliveira/pubs_files/

238 # CVD_Simulation/CVD_Simulation.html#Errata

239 L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M

240 M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L

241 S_a = cmfs[cmfs.wavelengths - d_S][:, 2]

242

243 LMS_a = tstack([L_a, M_a, S_a])

244 cmfs[cmfs.wavelengths] = LMS_a

245

246 severity = f"{d_L}, {d_M}, {d_S}"

247 template = "{0} - Anomalous Trichromacy ({1})"

248 cmfs.name = template.format(cmfs.name, severity)

249 cmfs.display_name = template.format(cmfs.display_name, severity)

250

251 return cmfs

252

253

254 def matrix_anomalous_trichromacy_Machado2009(

255 cmfs: LMS_ConeFundamentals,

256 primaries: RGB_DisplayPrimaries,

257 d_LMS: ArrayLike,

258) -> NDArrayFloat:

259 """

260 Compute the *Machado et al. (2009)* colour vision deficiency matrix for

261 anomalous trichromacy simulation.

262 primaries tri-spectral distributions with the specified :math:`\\Delta_{LMS}` shift

263 amount in nanometers to simulate anomalous trichromacy.

264

265 Parameters

266 ----------

267 cmfs

268 *LMS* cone fundamentals colour matching functions.

269 primaries

270 *RGB* display primaries tri-spectral distributions.

271 d_LMS

272 :math:`\\Delta_{LMS}` wavelength shift amount in nanometers for each

273 cone type.

274

275 Returns

276 -------

277 :class:`numpy.ndarray`

278 Anomalous trichromacy transformation matrix.

279

280 Notes

281 -----

282 - Input *LMS* cone fundamentals colour matching functions interval is

283 expected to be 1 nanometer, incompatible input will be interpolated

284 at 1 nanometer interval.

285 - Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20].

286

287 References

288 ----------

289 :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,

290 :cite:`Machado2009`

291

292 Examples

293 --------

294 >>> from colour.characterisation import MSDS_DISPLAY_PRIMARIES

295 >>> from colour.colorimetry import MSDS_CMFS_LMS

296 >>> cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 2 Degree Cone Fundamentals"]

297 >>> d_LMS = np.array([15, 0, 0])

298 >>> primaries = MSDS_DISPLAY_PRIMARIES["Apple Studio Display"]

299 >>> matrix_anomalous_trichromacy_Machado2009(cmfs, primaries, d_LMS)

300 ... # doctest: +ELLIPSIS

301 array([[-0.2777465..., 2.6515008..., -1.3737543...],

302 [ 0.2718936..., 0.2004786..., 0.5276276...],

303 [ 0.0064404..., 0.2592157..., 0.7343437...]])

304 """

305

306 if cmfs.shape.interval != 1:

307 cmfs = reshape_msds(

308 cmfs,

309 SpectralShape(cmfs.shape.start, cmfs.shape.end, 1),

310 "Interpolate",

311 copy=False,

312 )

313

314 M_n = matrix_RGB_to_WSYBRG(cmfs, primaries)

315 cmfs_a = msds_cmfs_anomalous_trichromacy_Machado2009(cmfs, d_LMS)

316 M_a = matrix_RGB_to_WSYBRG(cmfs_a, primaries)

317

318 return np.matmul(np.linalg.inv(M_n), M_a)

319

320

321 def matrix_cvd_Machado2009(

322 deficiency: Literal["Deuteranomaly", "Protanomaly", "Tritanomaly"] | str,

323 severity: float,

324) -> NDArrayFloat:

325 """

326 Compute the *Machado et al. (2009)* colour vision deficiency matrix for

327 the specified deficiency and severity using pre-computed matrices.

328

329 Parameters

330 ----------

331 deficiency

332 Colour vision deficiency type:

333

334 - *Protanomaly*: Defective long-wavelength cones (L-cones) with

335 reduced sensitivity. Complete absence of L-cones is

336 *Protanopia* or *red-dichromacy*.

337 - *Deuteranomaly*: Defective medium-wavelength cones (M-cones)

338 with peak sensitivity shifted towards red-sensitive cones.

339 Complete absence of M-cones is *Deuteranopia*.

340 - *Tritanomaly*: Defective short-wavelength cones (S-cones),

341 representing an alleviated form of blue-yellow colour

342 blindness. Complete absence of S-cones is *Tritanopia*.

343 severity

344 Severity of the colour vision deficiency in domain [0, 1].

345

346 Returns

347 -------

348 :class:`numpy.ndarray`

349 Colour vision deficiency matrix.

350

351 References

352 ----------

353 :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,

354 :cite:`Machado2009`

355

356 Examples

357 --------

358 >>> matrix_cvd_Machado2009("Protanomaly", 0.15) # doctest: +ELLIPSIS

359 array([[ 0.7869875..., 0.2694875..., -0.0564735...],

360 [ 0.0431695..., 0.933774 ..., 0.023058 ...],

361 [-0.004238 ..., -0.0024515..., 1.0066895...]])

362 """

363

364 if deficiency.lower() == "tritanomaly":

365 usage_warning(

366 '"Machado et al. (2009)" simulation of tritanomaly is based on '

367 "the shift paradigm as an approximation to the actual phenomenon "

368 "and restrain the model from trying to model tritanopia.\n"

369 "The pre-generated matrices are using a shift value in domain "

370 "[5, 59] contrary to the domain [0, 20] used for protanomaly and "

371 "deuteranomaly simulation."

372 )

373

374 matrices = CVD_MATRICES_MACHADO2010[deficiency]

375 samples = np.array(sorted(matrices.keys()))

376 index = as_int_scalar(

377 np.clip(np.searchsorted(samples, severity), 0, len(samples) - 1)

378 )

379

380 a = samples[index]

381 b = samples[min(index + 1, len(samples) - 1)]

382

383 m1, m2 = matrices[a], matrices[b]

384

385 if a == b:

386 # 1.0 severity colour vision deficiency matrix, returning directly.

387 return m1

388

389 return m1 + (severity - a) * ((m2 - m1) / (b - a))