LSST Applications g042eb84c57+730a74494b,g04e9c324dd+8c5ae1fdc5,g134cb467dc+1f1e3e7524,g199a45376c+0ba108daf9,g1fd858c14a+fa7d31856b,g210f2d0738+f66ac109ec,g262e1987ae+83a3acc0e5,g29ae962dfc+d856a2cb1f,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+a1e0c9f713,g47891489e3+0d594cb711,g4d44eb3520+c57ec8f3ed,g4d7b6aa1c5+f66ac109ec,g53246c7159+8c5ae1fdc5,g56a1a4eaf3+fd7ad03fde,g64539dfbff+f66ac109ec,g67b6fd64d1+0d594cb711,g67fd3c3899+f66ac109ec,g6985122a63+0d594cb711,g74acd417e5+3098891321,g786e29fd12+668abc6043,g81db2e9a8d+98e2ab9f28,g87389fa792+8856018cbb,g89139ef638+0d594cb711,g8d7436a09f+80fda9ce03,g8ea07a8fe4+760ca7c3fc,g90f42f885a+033b1d468d,g97be763408+a8a29bda4b,g99822b682c+e3ec3c61f9,g9d5c6a246b+0d5dac0c3d,ga41d0fce20+9243b26dd2,gbf99507273+8c5ae1fdc5,gd7ef33dd92+0d594cb711,gdab6d2f7ff+3098891321,ge410e46f29+0d594cb711,geaed405ab2+c4bbc419c6,gf9a733ac38+8c5ae1fdc5,w.2025.38
LSST Data Management Base Package
Loading...
Searching...
No Matches
_fallback_asinhmapping.py
Go to the documentation of this file.
1__all__ = ["AsinhMapping",]
2
3import numpy as np
4
5from astropy.visualization.lupton_rgb import compute_intensity
6
7
8class Mapping:
9 """
10 Baseclass to map red, blue, green intensities into uint8 values.
11 Parameters
12 ----------
13 minimum : float or sequence(3)
14 Intensity that should be mapped to black (a scalar or array for R, G, B).
15 image : ndarray, optional
16 An image used to calculate some parameters of some mappings.
17 """
18
19 def __init__(self, minimum=None, image=None):
20 self._uint8Max = float(np.iinfo(np.uint8).max)
21
22 try:
23 len(minimum)
24 except TypeError:
25 minimum = 3 * [minimum]
26 if len(minimum) != 3:
27 raise ValueError("please provide 1 or 3 values for minimum.")
28
29 self.minimum = minimum
30 self._image = np.asarray(image)
31
32 def make_rgb_image(self, image_r, image_g, image_b):
33 """
34 Convert 3 arrays, image_r, image_g, and image_b into an 8-bit RGB image.
35 Parameters
36 ----------
37 image_r : ndarray
38 Image to map to red.
39 image_g : ndarray
40 Image to map to green.
41 image_b : ndarray
42 Image to map to blue.
43 Returns
44 -------
45 RGBimage : ndarray
46 RGB (integer, 8-bits per channel) color image as an NxNx3 numpy array.
47 """
48 image_r = np.asarray(image_r)
49 image_g = np.asarray(image_g)
50 image_b = np.asarray(image_b)
51
52 if (image_r.shape != image_g.shape) or (image_g.shape != image_b.shape):
53 msg = "The image shapes must match. r: {}, g: {} b: {}"
54 raise ValueError(msg.format(image_r.shape, image_g.shape, image_b.shape))
55
56 return np.dstack(
57 self._convert_images_to_uint8(image_r, image_g, image_b)
58 ).astype(np.uint8)
59
60 def intensity(self, image_r, image_g, image_b):
61 """
62 Return the total intensity from the red, blue, and green intensities.
63 This is a naive computation, and may be overridden by subclasses.
64 Parameters
65 ----------
66 image_r : ndarray
67 Intensity of image to be mapped to red; or total intensity if
68 ``image_g`` and ``image_b`` are None.
69 image_g : ndarray, optional
70 Intensity of image to be mapped to green.
71 image_b : ndarray, optional
72 Intensity of image to be mapped to blue.
73 Returns
74 -------
75 intensity : ndarray
76 Total intensity from the red, blue and green intensities, or
77 ``image_r`` if green and blue images are not provided.
78 """
79 return compute_intensity(image_r, image_g, image_b)
80
81 def map_intensity_to_uint8(self, I): # noqa: E741
82 """
83 Return an array which, when multiplied by an image, returns that image
84 mapped to the range of a uint8, [0, 255] (but not converted to uint8).
85 The intensity is assumed to have had minimum subtracted (as that can be
86 done per-band).
87 Parameters
88 ----------
89 I : ndarray
90 Intensity to be mapped.
91 Returns
92 -------
93 mapped_I : ndarray
94 ``I`` mapped to uint8
95 """
96 with np.errstate(invalid="ignore", divide="ignore"):
97 return np.clip(I, 0, self._uint8Max)
98
99 def _convert_images_to_uint8(self, image_r, image_g, image_b):
100 """
101 Use the mapping to convert images image_r, image_g, and image_b to a triplet of uint8 images.
102 """
103 image_r = image_r - self.minimum[0] # n.b. makes copy
104 image_g = image_g - self.minimum[1]
105 image_b = image_b - self.minimum[2]
106
107 fac = self.map_intensity_to_uint8(self.intensity(image_r, image_g, image_b))
108
109 image_rgb = [image_r, image_g, image_b]
110 for c in image_rgb:
111 c *= fac
112 with np.errstate(invalid="ignore"):
113 c[c < 0] = 0 # individual bands can still be < 0, even if fac isn't
114
115 pixmax = self._uint8Max
116 # copies -- could work row by row to minimise memory usage
117 r0, g0, b0 = image_rgb
118
119 # n.b. np.where can't and doesn't short-circuit
120 with np.errstate(invalid="ignore", divide="ignore"):
121 for i, c in enumerate(image_rgb):
122 c = np.where(
123 r0 > g0,
124 np.where(
125 r0 > b0,
126 np.where(r0 >= pixmax, c * pixmax / r0, c),
127 np.where(b0 >= pixmax, c * pixmax / b0, c),
128 ),
129 np.where(
130 g0 > b0,
131 np.where(g0 >= pixmax, c * pixmax / g0, c),
132 np.where(b0 >= pixmax, c * pixmax / b0, c),
133 ),
134 ).astype(np.uint8)
135 c[c > pixmax] = pixmax
136
137 image_rgb[i] = c
138
139 return image_rgb
140
141
143 """
144 A linear map map of red, blue, green intensities into uint8 values.
145 A linear stretch from [minimum, maximum].
146 If one or both are omitted use image min and/or max to set them.
147 Parameters
148 ----------
149 minimum : float
150 Intensity that should be mapped to black (a scalar or array for R, G, B).
151 maximum : float
152 Intensity that should be mapped to white (a scalar).
153 """
154
155 def __init__(self, minimum=None, maximum=None, image=None):
156 if minimum is None or maximum is None:
157 if image is None:
158 raise ValueError(
159 "you must provide an image if you don't "
160 "set both minimum and maximum"
161 )
162 if minimum is None:
163 minimum = image.min()
164 if maximum is None:
165 maximum = image.max()
166
167 Mapping.__init__(self, minimum=minimum, image=image)
168 self.maximum = maximum
169
170 if maximum is None:
171 self._range = None
172 else:
173 if maximum == minimum:
174 raise ValueError("minimum and maximum values must not be equal")
175 self._range = float(maximum - minimum)
176
177 def map_intensity_to_uint8(self, I): # noqa: E741
178 # n.b. np.where can't and doesn't short-circuit
179 with np.errstate(invalid="ignore", divide="ignore"):
180 return np.where(
181 I <= 0,
182 0,
183 np.where(
184 I >= self._range, self._uint8Max / I, self._uint8Max / self._range
185 ),
186 )
187
188
190 """
191 A mapping for an asinh stretch (preserving colours independent of brightness).
192 x = asinh(Q (I - minimum)/stretch)/Q
193 This reduces to a linear stretch if Q == 0
194 See https://ui.adsabs.harvard.edu/abs/2004PASP..116..133L
195 Parameters
196 ----------
197 minimum : float
198 Intensity that should be mapped to black (a scalar or array for R, G, B).
199 stretch : float
200 The linear stretch of the image.
201 Q : float
202 The asinh softening parameter.
203 """
204
205 def __init__(self, minimum, stretch, Q=8):
206 Mapping.__init__(self, minimum)
207
208 # 32bit floating point machine epsilon; sys.float_info.epsilon is 64bit
209 epsilon = 1.0 / 2**23
210 if abs(Q) < epsilon:
211 Q = 0.1
212 else:
213 Qmax = 1e10
214 if Q > Qmax:
215 Q = Qmax
216
217 frac = 0.1 # gradient estimated using frac*stretch is _slope
218 self._slope = frac * self._uint8Max / np.arcsinh(frac * Q)
219
220 self._soften = Q / float(stretch)
221
222 def map_intensity_to_uint8(self, I): # noqa: E741
223 # n.b. np.where can't and doesn't short-circuit
224 with np.errstate(invalid="ignore", divide="ignore"):
225 return np.where(I <= 0, 0, np.arcsinh(I * self._soften) * self._slope / I)
__init__(self, minimum=None, maximum=None, image=None)
_convert_images_to_uint8(self, image_r, image_g, image_b)