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
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2DICOM - Grayscale Standard Display Function
3===========================================
5Define the *DICOM - Grayscale Standard Display Function* electro-optical
6transfer function (EOTF) and its inverse.
8- :func:`colour.models.eotf_inverse_DICOMGSDF`
9- :func:`colour.models.eotf_DICOMGSDF`
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.
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"""
26from __future__ import annotations
28import typing
30import numpy as np
32if typing.TYPE_CHECKING:
33 from colour.hints import NDArrayReal
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)
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"
56__all__ = [
57 "CONSTANTS_DICOMGSDF",
58 "eotf_inverse_DICOMGSDF",
59 "eotf_DICOMGSDF",
60]
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."""
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).
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.
105 Returns
106 -------
107 :class:`numpy.ndarray`
108 Just-Noticeable Difference (JND) Index, :math:`j`.
110 Notes
111 -----
112 +------------+-----------------------+---------------+
113 | **Domain** | **Scale - Reference** | **Scale - 1** |
114 +============+=======================+===============+
115 | ``L`` | 1 | 1 |
116 +------------+-----------------------+---------------+
118 +------------+-----------------------+---------------+
119 | **Range** | **Scale - Reference** | **Scale - 1** |
120 +============+=======================+===============+
121 | ``J`` | 1 | 1 |
122 +------------+-----------------------+---------------+
124 References
125 ----------
126 :cite:`NationalElectricalManufacturersAssociation2004b`
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 """
136 L = to_domain_1(L)
137 constants = optional(constants, CONSTANTS_DICOMGSDF)
139 L_lg = np.log10(L)
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
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 )
163 if out_int:
164 return as_int(np.round(J))
166 return as_float(from_range_1(J / 1023))
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).
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.
188 Returns
189 -------
190 :class:`numpy.ndarray`
191 *Luminance* :math:`L`.
193 Notes
194 -----
195 +------------+-----------------------+---------------+
196 | **Domain** | **Scale - Reference** | **Scale - 1** |
197 +============+=======================+===============+
198 | ``J`` | 1 | 1 |
199 +------------+-----------------------+---------------+
201 +------------+-----------------------+---------------+
202 | **Range** | **Scale - Reference** | **Scale - 1** |
203 +============+=======================+===============+
204 | ``L`` | 1 | 1 |
205 +------------+-----------------------+---------------+
207 References
208 ----------
209 :cite:`NationalElectricalManufacturersAssociation2004b`
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 """
219 J = to_domain_1(J)
220 constants = optional(constants, CONSTANTS_DICOMGSDF)
222 if not in_int:
223 J = J * 1023
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
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
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
247 return as_float(from_range_1(L))