Coverage for tests/test_rgb.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23Tests for RGB Images
25Run with:
26 python test_rgb.py
27or
28 pytest test_rgb.py
29"""
30import os
31import math
32import unittest
34import numpy as np
36import lsst.utils.tests
37import lsst.geom
38import lsst.afw.detection as afwDetect
39import lsst.afw.image as afwImage
40import lsst.afw.math as afwMath
41import lsst.afw.display as afwDisplay
42import lsst.afw.display.rgb as rgb
44ver1, ver2, ver3 = 1, 3, 1
45NO_MATPLOTLIB_STRING = "Requires matplotlib >= %d.%d.%d" % (ver1, ver2, ver3)
46try:
47 import matplotlib
48 mplVersion = matplotlib.__version__
49 # Split at + to check for development version (PEP 440)
50 mplVersion = mplVersion.split('+')
51 versionInfo = tuple(int(s.strip("rc")) for s in mplVersion[0].split("."))
52 HAVE_MATPLOTLIB = versionInfo >= (ver1, ver2, ver3)
53except ImportError:
54 HAVE_MATPLOTLIB = False
56try:
57 import scipy.misc
58 scipy.misc.imresize
59 HAVE_SCIPY_MISC = True
60except (ImportError, AttributeError):
61 HAVE_SCIPY_MISC = False
63try:
64 type(display)
65 afwDisplay.setDefaultMaskTransparency(75)
66except NameError:
67 display = False
70def saturate(image, satValue):
71 """Simulate saturation on an image, so we can test 'replaceSaturatedPixels'
73 Takes an Image, sets saturated pixels to NAN and masks them, returning
74 a MaskedImage.
75 """
76 image = afwImage.makeMaskedImage(image)
77 afwDetect.FootprintSet(image, afwDetect.Threshold(satValue), "SAT")
78 arr = image.getImage().getArray()
79 arr[np.where(arr >= satValue)] = np.nan
80 return image
83R, G, B = 2, 1, 0
86class RgbTestCase(unittest.TestCase):
87 """A test case for Rgb"""
89 def setUp(self):
90 self.min, self.range, self.Q = 0, 5, 20 # asinh
92 width, height = 85, 75
93 self.images = []
94 self.images.append(afwImage.ImageF(lsst.geom.ExtentI(width, height)))
95 self.images.append(afwImage.ImageF(lsst.geom.ExtentI(width, height)))
96 self.images.append(afwImage.ImageF(lsst.geom.ExtentI(width, height)))
98 for (x, y, A, g_r, r_i) in [(15, 15, 1000, 1.0, 2.0),
99 (50, 45, 5500, -1.0, -0.5),
100 (30, 30, 600, 1.0, 2.5),
101 (45, 15, 20000, 1.0, 1.0),
102 ]:
103 for i in range(len(self.images)):
104 if i == B:
105 amp = A
106 elif i == G:
107 amp = A*math.pow(10, 0.4*g_r)
108 elif i == R:
109 amp = A*math.pow(10, 0.4*r_i)
111 self.images[i][x, y, afwImage.LOCAL] = amp
113 psf = afwMath.AnalyticKernel(
114 15, 15, afwMath.GaussianFunction2D(2.5, 1.5, 0.5))
116 convolvedImage = type(self.images[0])(self.images[0].getDimensions())
117 randomImage = type(self.images[0])(self.images[0].getDimensions())
118 rand = afwMath.Random("MT19937", 666)
119 for i in range(len(self.images)):
120 afwMath.convolve(convolvedImage, self.images[i], psf, True, True)
121 afwMath.randomGaussianImage(randomImage, rand)
122 randomImage *= 2
123 convolvedImage += randomImage
124 self.images[i][:] = convolvedImage
125 del convolvedImage
126 del randomImage
128 def tearDown(self):
129 for im in self.images:
130 del im
131 del self.images
133 def testStarsAsinh(self):
134 """Test creating an RGB image using an asinh stretch"""
135 asinhMap = rgb.AsinhMapping(self.min, self.range, self.Q)
136 rgbImage = asinhMap.makeRgbImage(
137 self.images[R], self.images[G], self.images[B])
139 if display:
140 rgb.displayRGB(rgbImage)
142 def testStarsAsinhZscale(self):
143 """Test creating an RGB image using an asinh stretch estimated using zscale"""
145 rgbImages = [self.images[R], self.images[G], self.images[B]]
147 map = rgb.AsinhZScaleMapping(rgbImages[0])
148 rgbImage = map.makeRgbImage(*rgbImages)
150 if display:
151 rgb.displayRGB(rgbImage)
153 def testStarsAsinhZscaleIntensity(self):
154 """Test creating an RGB image using an asinh stretch estimated using zscale on the intensity"""
156 rgbImages = [self.images[R], self.images[G], self.images[B]]
158 map = rgb.AsinhZScaleMapping(rgbImages)
159 rgbImage = map.makeRgbImage(*rgbImages)
161 if display:
162 rgb.displayRGB(rgbImage)
164 def testStarsAsinhZscaleIntensityPedestal(self):
165 """Test creating an RGB image using an asinh stretch estimated using zscale on the intensity
166 where the images each have a pedestal added"""
168 rgbImages = [self.images[R], self.images[G], self.images[B]]
170 pedestal = [100, 400, -400]
171 for i, ped in enumerate(pedestal):
172 rgbImages[i] += ped
174 map = rgb.AsinhZScaleMapping(rgbImages, pedestal=pedestal)
175 rgbImage = map.makeRgbImage(*rgbImages)
177 if display:
178 rgb.displayRGB(rgbImage)
180 def testStarsAsinhZscaleIntensityBW(self):
181 """Test creating a black-and-white image using an asinh stretch estimated
182 using zscale on the intensity"""
184 rgbImage = rgb.AsinhZScaleMapping(self.images[R]).makeRgbImage()
186 if display:
187 rgb.displayRGB(rgbImage)
189 @unittest.skipUnless(HAVE_MATPLOTLIB, NO_MATPLOTLIB_STRING)
190 def testMakeRGB(self):
191 """Test the function that does it all"""
192 satValue = 1000.0
193 with lsst.utils.tests.getTempFilePath(".png") as fileName:
194 red = saturate(self.images[R], satValue)
195 green = saturate(self.images[G], satValue)
196 blue = saturate(self.images[B], satValue)
197 rgb.makeRGB(red, green, blue, self.min, self.range, self.Q, fileName=fileName,
198 saturatedBorderWidth=1, saturatedPixelValue=2000)
199 self.assertTrue(os.path.exists(fileName))
201 def testLinear(self):
202 """Test using a specified linear stretch"""
204 rgbImage = rgb.LinearMapping(-8.45, 13.44).makeRgbImage(self.images[R])
206 if display:
207 rgb.displayRGB(rgbImage)
209 def testLinearMinMax(self):
210 """Test using a min/max linear stretch
212 N.b. also checks that an image passed to the ctor is used as the default in makeRgbImage()
213 """
215 rgbImage = rgb.LinearMapping(image=self.images[R]).makeRgbImage()
217 if display:
218 rgb.displayRGB(rgbImage)
220 def testZScale(self):
221 """Test using a zscale stretch"""
223 rgbImage = rgb.ZScaleMapping(self.images[R]).makeRgbImage()
225 if display:
226 plt = rgb.displayRGB(rgbImage, False)
227 plt.title("zscale")
228 plt.show()
230 @unittest.skipUnless(HAVE_MATPLOTLIB, NO_MATPLOTLIB_STRING)
231 def testWriteStars(self):
232 """Test writing RGB files to disk"""
233 asinhMap = rgb.AsinhMapping(self.min, self.range, self.Q)
234 rgbImage = asinhMap.makeRgbImage(
235 self.images[R], self.images[G], self.images[B])
236 with lsst.utils.tests.getTempFilePath(".png") as fileName:
237 rgb.writeRGB(fileName, rgbImage)
238 self.assertTrue(os.path.exists(fileName))
240 def testSaturated(self):
241 """Test interpolating saturated pixels"""
243 satValue = 1000.0
244 for f in [R, G, B]:
245 self.images[f] = saturate(self.images[f], satValue)
247 rgb.replaceSaturatedPixels(
248 self.images[R], self.images[G], self.images[B], 1, 2000)
249 #
250 # Check that we replaced those NaNs with some reasonable value
251 #
252 for f in [R, G, B]:
253 self.assertTrue(np.isfinite(
254 self.images[f].getImage().getArray()).all())
256 if display > 1:
257 afwDisplay.Display(frame=0).mtv(self.images[B], title="B: SAT-interpolated")
258 afwDisplay.Display(frame=1).mtv(self.images[G], title="G: SAT-interpolated")
259 afwDisplay.Display(frame=2).mtv(self.images[R], title="R: SAT-interpolated")
260 #
261 # Prepare for generating an output file
262 #
263 for f in [R, G, B]:
264 self.images[f] = self.images[f].getImage()
266 asinhMap = rgb.AsinhMapping(self.min, self.range, self.Q)
267 rgbImage = asinhMap.makeRgbImage(
268 self.images[R], self.images[G], self.images[B])
270 if display:
271 rgb.displayRGB(rgbImage)
273 @unittest.skipUnless(HAVE_SCIPY_MISC, "Resizing images requires scipy.misc")
274 def testStarsResizeToSize(self):
275 """Test creating an RGB image of a specified size"""
277 xSize = self.images[R].getWidth()//2
278 ySize = self.images[R].getHeight()//2
279 for rgbImages in ([self.images[R], self.images[G], self.images[B]],
280 [afwImage.ImageU(_.getArray().astype('uint16')) for _ in [
281 self.images[R], self.images[G], self.images[B]]]):
282 rgbImage = rgb.AsinhZScaleMapping(rgbImages[0]).makeRgbImage(*rgbImages,
283 xSize=xSize, ySize=ySize)
285 if display:
286 rgb.displayRGB(rgbImage)
288 @unittest.skipUnless(HAVE_SCIPY_MISC, "Resizing images requires scipy.misc")
289 def testStarsResizeSpecifications(self):
290 """Test creating an RGB image changing the output """
292 rgbImages = [self.images[R], self.images[G], self.images[B]]
293 map = rgb.AsinhZScaleMapping(rgbImages[0])
295 for xSize, ySize, frac in [(self.images[R].getWidth()//2, self.images[R].getHeight()//2, None),
296 (2*self.images[R].getWidth(), None, None),
297 (self.images[R].getWidth()//2, None, None),
298 (None, self.images[R].getHeight()//2, None),
299 (None, None, 0.5),
300 (None, None, 2),
301 ]:
302 rgbImage = map.makeRgbImage(
303 *rgbImages, xSize=xSize, ySize=ySize, rescaleFactor=frac)
305 h, w = rgbImage.shape[0:2]
306 self.assertTrue(xSize is None or xSize == w)
307 self.assertTrue(ySize is None or ySize == h)
308 self.assertTrue(frac is None or w == int(frac*self.images[R].getWidth()),
309 "%g == %g" % (w, int((frac if frac else 1)*self.images[R].getWidth())))
311 if display:
312 rgb.displayRGB(rgbImage)
314 @unittest.skipUnless(HAVE_SCIPY_MISC, "Resizing images requires scipy.misc")
315 @unittest.skipUnless(HAVE_MATPLOTLIB, NO_MATPLOTLIB_STRING)
316 def testMakeRGBResize(self):
317 """Test the function that does it all, including rescaling"""
318 rgb.makeRGB(self.images[R], self.images[G],
319 self.images[B], xSize=40, ySize=60)
321 with lsst.utils.tests.getTempFilePath(".png") as fileName:
322 rgb.makeRGB(self.images[R], self.images[G],
323 self.images[B], fileName=fileName, rescaleFactor=0.5)
324 self.assertTrue(os.path.exists(fileName))
326 def writeFileLegacyAPI(self, fileName):
327 """Test that the legacy API still works, although it's deprecated"""
328 with self.assertWarns(FutureWarning):
329 asinh = rgb.asinhMappingF(self.min, self.range, self.Q)
330 rgbImage = rgb.RgbImageF(
331 self.images[R], self.images[G], self.images[B], asinh)
332 if display > 1:
333 afwDisplay.Display(frame=0).mtv(self.images[B], title="B: legacy API")
334 afwDisplay.Display(frame=1).mtv(self.images[G], title="G: legacy API")
335 afwDisplay.Display(frame=2).mtv(self.images[R], title="R: legacy API")
337 rgbImage.write(fileName)
339 @unittest.skipUnless(HAVE_MATPLOTLIB, NO_MATPLOTLIB_STRING)
340 def testWriteStarsLegacyAPI(self):
341 with lsst.utils.tests.getTempFilePath(".png") as fileName:
342 self.writeFileLegacyAPI(fileName)
343 self.assertTrue(os.path.exists(fileName))
346class TestMemory(lsst.utils.tests.MemoryTestCase):
347 pass
350def setup_module(module):
351 lsst.utils.tests.init()
354if __name__ == "__main__": 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true
355 lsst.utils.tests.init()
356 unittest.main()