Hide keyboard shortcuts

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/>. 

21 

22""" 

23Tests for RGB Images 

24 

25Run with: 

26 python test_rgb.py 

27or 

28 pytest test_rgb.py 

29""" 

30import os 

31import math 

32import unittest 

33 

34import numpy as np 

35 

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 

43 

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 

55 

56try: 

57 import scipy.misc 

58 scipy.misc.imresize 

59 HAVE_SCIPY_MISC = True 

60except (ImportError, AttributeError): 

61 HAVE_SCIPY_MISC = False 

62 

63try: 

64 type(display) 

65 afwDisplay.setDefaultMaskTransparency(75) 

66except NameError: 

67 display = False 

68 

69 

70def saturate(image, satValue): 

71 """Simulate saturation on an image, so we can test 'replaceSaturatedPixels' 

72 

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 

81 

82 

83R, G, B = 2, 1, 0 

84 

85 

86class RgbTestCase(unittest.TestCase): 

87 """A test case for Rgb""" 

88 

89 def setUp(self): 

90 self.min, self.range, self.Q = 0, 5, 20 # asinh 

91 

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

97 

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) 

110 

111 self.images[i][x, y, afwImage.LOCAL] = amp 

112 

113 psf = afwMath.AnalyticKernel( 

114 15, 15, afwMath.GaussianFunction2D(2.5, 1.5, 0.5)) 

115 

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 

127 

128 def tearDown(self): 

129 for im in self.images: 

130 del im 

131 del self.images 

132 

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]) 

138 

139 if display: 

140 rgb.displayRGB(rgbImage) 

141 

142 def testStarsAsinhZscale(self): 

143 """Test creating an RGB image using an asinh stretch estimated using zscale""" 

144 

145 rgbImages = [self.images[R], self.images[G], self.images[B]] 

146 

147 map = rgb.AsinhZScaleMapping(rgbImages[0]) 

148 rgbImage = map.makeRgbImage(*rgbImages) 

149 

150 if display: 

151 rgb.displayRGB(rgbImage) 

152 

153 def testStarsAsinhZscaleIntensity(self): 

154 """Test creating an RGB image using an asinh stretch estimated using zscale on the intensity""" 

155 

156 rgbImages = [self.images[R], self.images[G], self.images[B]] 

157 

158 map = rgb.AsinhZScaleMapping(rgbImages) 

159 rgbImage = map.makeRgbImage(*rgbImages) 

160 

161 if display: 

162 rgb.displayRGB(rgbImage) 

163 

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""" 

167 

168 rgbImages = [self.images[R], self.images[G], self.images[B]] 

169 

170 pedestal = [100, 400, -400] 

171 for i, ped in enumerate(pedestal): 

172 rgbImages[i] += ped 

173 

174 map = rgb.AsinhZScaleMapping(rgbImages, pedestal=pedestal) 

175 rgbImage = map.makeRgbImage(*rgbImages) 

176 

177 if display: 

178 rgb.displayRGB(rgbImage) 

179 

180 def testStarsAsinhZscaleIntensityBW(self): 

181 """Test creating a black-and-white image using an asinh stretch estimated 

182 using zscale on the intensity""" 

183 

184 rgbImage = rgb.AsinhZScaleMapping(self.images[R]).makeRgbImage() 

185 

186 if display: 

187 rgb.displayRGB(rgbImage) 

188 

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

200 

201 def testLinear(self): 

202 """Test using a specified linear stretch""" 

203 

204 rgbImage = rgb.LinearMapping(-8.45, 13.44).makeRgbImage(self.images[R]) 

205 

206 if display: 

207 rgb.displayRGB(rgbImage) 

208 

209 def testLinearMinMax(self): 

210 """Test using a min/max linear stretch 

211 

212 N.b. also checks that an image passed to the ctor is used as the default in makeRgbImage() 

213 """ 

214 

215 rgbImage = rgb.LinearMapping(image=self.images[R]).makeRgbImage() 

216 

217 if display: 

218 rgb.displayRGB(rgbImage) 

219 

220 def testZScale(self): 

221 """Test using a zscale stretch""" 

222 

223 rgbImage = rgb.ZScaleMapping(self.images[R]).makeRgbImage() 

224 

225 if display: 

226 plt = rgb.displayRGB(rgbImage, False) 

227 plt.title("zscale") 

228 plt.show() 

229 

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

239 

240 def testSaturated(self): 

241 """Test interpolating saturated pixels""" 

242 

243 satValue = 1000.0 

244 for f in [R, G, B]: 

245 self.images[f] = saturate(self.images[f], satValue) 

246 

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()) 

255 

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() 

265 

266 asinhMap = rgb.AsinhMapping(self.min, self.range, self.Q) 

267 rgbImage = asinhMap.makeRgbImage( 

268 self.images[R], self.images[G], self.images[B]) 

269 

270 if display: 

271 rgb.displayRGB(rgbImage) 

272 

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""" 

276 

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) 

284 

285 if display: 

286 rgb.displayRGB(rgbImage) 

287 

288 @unittest.skipUnless(HAVE_SCIPY_MISC, "Resizing images requires scipy.misc") 

289 def testStarsResizeSpecifications(self): 

290 """Test creating an RGB image changing the output """ 

291 

292 rgbImages = [self.images[R], self.images[G], self.images[B]] 

293 map = rgb.AsinhZScaleMapping(rgbImages[0]) 

294 

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) 

304 

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()))) 

310 

311 if display: 

312 rgb.displayRGB(rgbImage) 

313 

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) 

320 

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

325 

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") 

336 

337 rgbImage.write(fileName) 

338 

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

344 

345 

346class TestMemory(lsst.utils.tests.MemoryTestCase): 

347 pass 

348 

349 

350def setup_module(module): 

351 lsst.utils.tests.init() 

352 

353 

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()