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

1"""

2 DICOM - Grayscale Standard Display Function

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

4

5 Define the *DICOM - Grayscale Standard Display Function* electro-optical

6 transfer function (EOTF) and its inverse.

7

8- :func:`colour.models.eotf_inverse_DICOMGSDF`

9- :func:`colour.models.eotf_DICOMGSDF`

10

11 The Grayscale Standard Display Function is defined for the Luminance Range from

12:math:`0.05` to :math:`4000 cd/m^2`. The minimum Luminance corresponds to the

13 lowest practically useful Luminance of cathode-ray-tube (CRT) monitors and the

14 maximum exceeds the unattenuated Luminance of very bright light-boxes used for

15 interpreting X-Ray mammography. The Grayscale Standard Display Function

16 explicitly includes the effects of the diffused ambient Illuminance.

17

18 References

19----------

20- :cite:`NationalElectricalManufacturersAssociation2004b` : National

21 Electrical Manufacturers Association. (2004). Digital Imaging and

22 Communications in Medicine (DICOM) Part 14: Grayscale Standard Display

23 Function. http://medical.nema.org/dicom/2004/04_14PU.PDF

24"""

25

26 from __future__ import annotations

27

28 import typing

29

30 import numpy as np

31

32 if typing.TYPE_CHECKING:

33 from colour.hints import NDArrayReal

34

35 from colour.hints import ( # noqa: TC001

36 Annotated,

37 Domain1,

38 Range1,

39)

40 from colour.utilities import (

41 Structure,

42 as_float,

43 as_int,

44 from_range_1,

45 optional,

46 to_domain_1,

47)

48

49 __author__ = "Colour Developers"

50 __copyright__ = "Copyright 2013 Colour Developers"

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

52 __maintainer__ = "Colour Developers"

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

54 __status__ = "Production"

55

56 __all__ = [

57 "CONSTANTS_DICOMGSDF",

58 "eotf_inverse_DICOMGSDF",

59 "eotf_DICOMGSDF",

60]

61

62 CONSTANTS_DICOMGSDF: Structure = Structure(

63 a=-1.3011877,

64 b=-2.5840191e-2,

65 c=8.0242636e-2,

66 d=-1.0320229e-1,

67 e=1.3646699e-1,

68 f=2.8745620e-2,

69 g=-2.5468404e-2,

70 h=-3.1978977e-3,

71 k=1.2992634e-4,

72 m=1.3635334e-3,

73 A=71.498068,

74 B=94.593053,

75 C=41.912053,

76 D=9.8247004,

77 E=0.28175407,

78 F=-1.1878455,

79 G=-0.18014349,

80 H=0.14710899,

81 I=-0.017046845,

82)

83"""*DICOM Grayscale Standard Display Function* constants."""

84

85

86 def eotf_inverse_DICOMGSDF(

87 L: Domain1,

88 out_int: bool = False,

89 constants: Structure | None = None,

90) -> Annotated[NDArrayReal, 1]:

91 """

92 Apply the *DICOM - Grayscale Standard Display Function* inverse

93 electro-optical transfer function (EOTF).

94

95 Parameters

96 ----------

97 L

98 *Luminance* :math:`L`.

99 out_int

100 Whether to return value as integer code value or float equivalent

101 of a code value at a specified bit-depth.

102 constants

103 *DICOM - Grayscale Standard Display Function* constants.

104

105 Returns

106 -------

107 :class:`numpy.ndarray`

108 Just-Noticeable Difference (JND) Index, :math:`j`.

109

110 Notes

111 -----

112 +------------+-----------------------+---------------+

113 | **Domain** | **Scale - Reference** | **Scale - 1** |

114 +============+=======================+===============+

115 | ``L`` | 1 | 1 |

116 +------------+-----------------------+---------------+

117

118 +------------+-----------------------+---------------+

119 | **Range** | **Scale - Reference** | **Scale - 1** |

120 +============+=======================+===============+

121 | ``J`` | 1 | 1 |

122 +------------+-----------------------+---------------+

123

124 References

125 ----------

126 :cite:`NationalElectricalManufacturersAssociation2004b`

127

128 Examples

129 --------

130 >>> eotf_inverse_DICOMGSDF(130.0662) # doctest: +ELLIPSIS

131 0.5004862...

132 >>> eotf_inverse_DICOMGSDF(130.0662, out_int=True)

133 512

134 """

135

136 L = to_domain_1(L)

137 constants = optional(constants, CONSTANTS_DICOMGSDF)

138

139 L_lg = np.log10(L)

140

141 A = constants.A

142 B = constants.B

143 C = constants.C

144 D = constants.D

145 E = constants.E

146 F = constants.F

147 G = constants.G

148 H = constants.H

149 I = constants.I # noqa: E741

150

151 J = (

152 A

153 + B * L_lg

154 + C * L_lg**2

155 + D * L_lg**3

156 + E * L_lg**4

157 + F * L_lg**5

158 + G * L_lg**6

159 + H * L_lg**7

160 + I * L_lg**8

161 )

162

163 if out_int:

164 return as_int(np.round(J))

165

166 return as_float(from_range_1(J / 1023))

167

168

169 def eotf_DICOMGSDF(

170 J: Domain1,

171 in_int: bool = False,

172 constants: Structure | None = None,

173) -> Range1:

174 """

175 Apply the *DICOM - Grayscale Standard Display Function* electro-optical

176 transfer function (EOTF).

177

178 Parameters

179 ----------

180 J

181 Just-Noticeable Difference (JND) Index, :math:`j`.

182 in_int

183 Whether to treat the input value as integer code value or float

184 equivalent of a code value at a specified bit-depth.

185 constants

186 *DICOM - Grayscale Standard Display Function* constants.

187

188 Returns

189 -------

190 :class:`numpy.ndarray`

191 *Luminance* :math:`L`.

192

193 Notes

194 -----

195 +------------+-----------------------+---------------+

196 | **Domain** | **Scale - Reference** | **Scale - 1** |

197 +============+=======================+===============+

198 | ``J`` | 1 | 1 |

199 +------------+-----------------------+---------------+

200

201 +------------+-----------------------+---------------+

202 | **Range** | **Scale - Reference** | **Scale - 1** |

203 +============+=======================+===============+

204 | ``L`` | 1 | 1 |

205 +------------+-----------------------+---------------+

206

207 References

208 ----------

209 :cite:`NationalElectricalManufacturersAssociation2004b`

210

211 Examples

212 --------

213 >>> eotf_DICOMGSDF(0.500486263438448) # doctest: +ELLIPSIS

214 130.0628647...

215 >>> eotf_DICOMGSDF(512, in_int=True) # doctest: +ELLIPSIS

216 130.0652840...

217 """

218

219 J = to_domain_1(J)

220 constants = optional(constants, CONSTANTS_DICOMGSDF)

221

222 if not in_int:

223 J = J * 1023

224

225 a = constants.a

226 b = constants.b

227 c = constants.c

228 d = constants.d

229 e = constants.e

230 f = constants.f

231 g = constants.g

232 h = constants.h

233 k = constants.k

234 m = constants.m

235

236 J_ln = np.log(J)

237 J_ln2 = J_ln**2

238 J_ln3 = J_ln**3

239 J_ln4 = J_ln**4

240 J_ln5 = J_ln**5

241

242 L = (a + c * J_ln + e * J_ln2 + g * J_ln3 + m * J_ln4) / (

243 1 + b * J_ln + d * J_ln2 + f * J_ln3 + h * J_ln4 + k * J_ln5

244 )

245 L = 10**L

246

247 return as_float(from_range_1(L))