Coverage for colour/models/rgb/transfer_functions/dicom_gsdf.py: 100%

54 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2DICOM - Grayscale Standard Display Function 

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

4 

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

6transfer function (EOTF) and its inverse. 

7 

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

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

10 

11The 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 

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

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

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

16explicitly includes the effects of the diffused ambient Illuminance. 

17 

18References 

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 

26from __future__ import annotations 

27 

28import typing 

29 

30import numpy as np 

31 

32if typing.TYPE_CHECKING: 

33 from colour.hints import NDArrayReal 

34 

35from colour.hints import ( # noqa: TC001 

36 Annotated, 

37 Domain1, 

38 Range1, 

39) 

40from 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 

62CONSTANTS_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 

86def 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 

169def 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))