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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

from builtins import object 

import numpy as np 

import glob 

import os 

import healpy as hp 

from lsst.utils import getPackageDir 

import warnings 

from lsst.sims.utils import _angularSeparation 

 

__all__ = ['SkyModelPre', 'interp_angle'] 

 

 

def shortAngleDist(a0, a1): 

""" 

from https://gist.github.com/shaunlebron/8832585 

""" 

max_angle = 2.*np.pi 

da = (a1 - a0) % max_angle 

return 2.*da % max_angle - da 

 

 

def interp_angle(x_out, xp, anglep, degrees=False): 

""" 

Interpolate angle values (handle wrap around properly). Does nearest neighbor 

interpolation if values out of range. 

 

Parameters 

---------- 

x_out : float (or array) 

The points to interpolate to. 

xp : array 

Points to interpolate between (must be sorted) 

anglep : array 

The angles ascociated with xp 

degrees : bool (False) 

Set if anglep is degrees (True) or radidian (False) 

""" 

 

# Where are the interpolation points 

x = np.atleast_1d(x_out) 

left = np.searchsorted(xp, x)-1 

right = left+1 

 

# If we are out of bounds, just use the edges 

right[np.where(right >= xp.size)] -= 1 

left[np.where(left < 0)] += 1 

baseline = xp[right] - xp[left] 

 

wterm = (x - xp[left])/baseline 

wterm[np.where(baseline == 0)] = 0 

51 ↛ 52line 51 didn't jump to line 52, because the condition on line 51 was never true if degrees: 

result = np.radians(anglep[left]) + shortAngleDist(np.radians(anglep[left]), np.radians(anglep[right]))*wterm 

result = result % (2.*np.pi) 

result = np.degrees(result) 

else: 

result = anglep[left] + shortAngleDist(anglep[left], anglep[right])*wterm 

result = result % (2.*np.pi) 

return result 

 

 

class SkyModelPre(object): 

""" 

Load pre-computed sky brighntess maps for the LSST site and use them to interpolate to 

arbitrary dates. 

""" 

 

def __init__(self, data_path=None, opsimFields=False, 

speedLoad=True, verbose=False): 

""" 

Parameters 

---------- 

data_path : str (None) 

path to the numpy save files. Looks in standard plances if set to None. 

opsimFields : bool (False) 

Mostly depreciated, if True, loads sky brightnesses computed at field centers. 

Otherwise uses healpixels. 

speedLoad : bool (True) 

If True, use the small 3-day file to load found in the usual spot. 

""" 

 

self.info = None 

self.sb = None 

self.opsimFields = opsimFields 

self.verbose = verbose 

 

# Look in default location for .npz files to load 

87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true if 'SIMS_SKYBRIGHTNESS_DATA' in os.environ: 

data_dir = os.environ['SIMS_SKYBRIGHTNESS_DATA'] 

else: 

data_dir = os.path.join(getPackageDir('sims_skybrightness_pre'), 'data') 

 

92 ↛ 98line 92 didn't jump to line 98, because the condition on line 92 was never false if data_path is None: 

93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true if opsimFields: 

data_path = os.path.join(data_dir, 'opsimFields') 

else: 

data_path = os.path.join(data_dir, 'healpix') 

 

self.files = glob.glob(os.path.join(data_path, '*.npz*')) 

99 ↛ 100line 99 didn't jump to line 100, because the condition on line 99 was never true if len(self.files) == 0: 

errmssg = 'Failed to find pre-computed .npz files. ' 

errmssg += 'Copy data from NCSA with sims_skybrightness_pre/data/data_down.sh \n' 

errmssg += 'or build by running sims_skybrightness_pre/data/generate_sky.py' 

warnings.warn(errmssg) 

mjd_left = [] 

mjd_right = [] 

# Expect filenames of the form mjd1_mjd2.npz, e.g., 59632.155_59633.2.npz 

big_files = glob.glob(os.path.join(data_path, '*.npz')) 

108 ↛ 110line 108 didn't jump to line 110, because the condition on line 108 was never false if len(big_files) != 0: 

self.files = big_files 

for filename in big_files: 

temp = os.path.split(filename)[-1].replace('.npz', '').split('_') 

mjd_left.append(float(temp[0])) 

mjd_right.append(float(temp[1])) 

 

self.mjd_left = np.array(mjd_left) 

self.mjd_right = np.array(mjd_right) 

 

# Set that nothing is loaded at this point 

self.loaded_range = np.array([-1]) 

 

# Go ahead and load the small one in the repo by default 

122 ↛ exitline 122 didn't return from function '__init__', because the condition on line 122 was never false if speedLoad: 

self._load_data(59853., 

filename=os.path.join(data_dir, 'healpix/59853_59856.npz'), 

npyfile=os.path.join(data_dir, 'healpix/59853_59856.npy')) 

 

def _load_data(self, mjd, filename=None, npyfile=None): 

""" 

Load up the .npz file to interpolate things. After python 3 upgrade, numpy.savez refused 

to write large .npz files, so data is split between .npz and .npy files. 

 

Parameters 

---------- 

mjd : float 

The Modified Julian Date that we want to load 

filename : str (None) 

The filename to restore. If None, it checks the filenames on disk to find one that 

should have the requested MJD 

npyfile : str (None) 

If sky brightness data not in npz file, checks the .npy file with same root name. 

""" 

 

143 ↛ 145line 143 didn't jump to line 145, because the condition on line 143 was never true if filename is None: 

# Figure out which file to load. 

file_indx = np.where((mjd >= self.mjd_left) & (mjd <= self.mjd_right))[0] 

if np.size(file_indx) == 0: 

raise ValueError('MJD = %f is out of range for the files found (%f-%f)' % (mjd, 

self.mjd_left.min(), 

self.mjd_right.max())) 

filename = self.files[file_indx.max()] 

self.loaded_range = np.array([self.mjd_left[file_indx], self.mjd_right[file_indx]]) 

else: 

self.loaded_range = None 

 

155 ↛ 156line 155 didn't jump to line 156, because the condition on line 155 was never true if self.verbose: 

print('Loading file %s' % filename) 

# Add encoding kwarg to restore Python 2.7 generated files 

data = np.load(filename, encoding='bytes') 

self.info = data['dict_of_lists'][()] 

self.header = data['header'][()] 

161 ↛ 162line 161 didn't jump to line 162, because the condition on line 161 was never true if 'sky_brightness' in data.keys(): 

self.sb = data['sky_brightness'][()] 

data.close() 

else: 

# the sky brightness had to go in it's own npy file 

data.close() 

167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true if npyfile is None: 

npyfile = filename[:-3]+'npy' 

self.sb = np.load(npyfile) 

170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true if self.verbose: 

print('also loading %s' % npyfile) 

 

# Step to make sure keys are strings not bytes 

all_dicts = [self.info, self.sb, self.header] 

all_dicts = [single_dict for single_dict in all_dicts if hasattr(single_dict, 'keys')] 

for selfDict in all_dicts: 

for key in list(selfDict.keys()): 

178 ↛ 179line 178 didn't jump to line 179, because the condition on line 178 was never true if type(key) != str: 

selfDict[key.decode("utf-8")] = selfDict.pop(key) 

 

# Ugh, different versions of the save files could have dicts or np.array. 

# Let's hope someone fits some Fourier components to the sky brightnesses and gets rid 

# of the giant save files for good. 

184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true if hasattr(self.sb, 'keys'): 

self.filter_names = list(self.sb.keys()) 

else: 

self.filter_names = self.sb.dtype.names 

 

189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true if self.verbose: 

print('%s loaded' % os.path.split(filename)[1]) 

 

192 ↛ 195line 192 didn't jump to line 195, because the condition on line 192 was never false if not self.opsimFields: 

self.nside = hp.npix2nside(self.sb[self.filter_names[0]][0, :].size) 

 

195 ↛ exitline 195 didn't return from function '_load_data', because the condition on line 195 was never false if self.loaded_range is None: 

self.loaded_range = np.array([self.info['mjds'].min(), self.info['mjds'].max()]) 

 

def returnSunMoon(self, mjd): 

""" 

Parameters 

---------- 

mjd : float 

Modified Julian Date(s) to interpolate to 

 

Returns 

------- 

sunMoon : dict 

Dict with keys for the sun and moon RA and Dec and the 

mooon-sun separation. All values in radians, except for moonSunSep 

that is in degrees for some reason (that reason is probably because I'm sloppy). 

""" 

 

#warnings.warn('Method returnSunMoon to be depreciated. Interpolating angles is bad!') 

 

keys = ['sunAlts', 'moonAlts', 'moonRAs', 'moonDecs', 'sunRAs', 

'sunDecs', 'moonSunSep'] 

 

degrees = [False, False, False, False, False, False, True] 

 

220 ↛ 221line 220 didn't jump to line 221, because the condition on line 220 was never true if (mjd < self.loaded_range.min() or (mjd > self.loaded_range.max())): 

self._load_data(mjd) 

 

result = {} 

for key, degree in zip(keys, degrees): 

if key[-1] == 's': 

newkey = key[:-1] 

else: 

newkey = key 

if 'RA' in key: 

result[newkey] = interp_angle(mjd, self.info['mjds'], self.info[key], degrees=degree) 

# Return a scalar if only doing 1 date. 

232 ↛ 224line 232 didn't jump to line 224, because the condition on line 232 was never false if np.size(result[newkey]) == 1: 

result[newkey] = np.max(result[newkey]) 

else: 

result[newkey] = np.interp(mjd, self.info['mjds'], self.info[key]) 

return result 

 

def returnAirmass(self, mjd, maxAM=10., indx=None, badval=hp.UNSEEN): 

""" 

 

Parameters 

---------- 

mjd : float 

Modified Julian Date to interpolate to 

indx : List of int(s) (None) 

indices to interpolate the sky values at. Returns full sky if None. If the class was 

instatiated with opsimFields, indx is the field ID, otherwise it is the healpix ID. 

maxAM : float (10) 

The maximum airmass to return, everything above this airmass will be set to badval 

 

Returns 

------- 

airmass : np.array 

Array of airmass values. If the MJD is between sunrise and sunset, all values are masked. 

""" 

256 ↛ 257line 256 didn't jump to line 257, because the condition on line 256 was never true if (mjd < self.loaded_range.min() or (mjd > self.loaded_range.max())): 

self._load_data(mjd) 

 

left = np.searchsorted(self.info['mjds'], mjd)-1 

right = left+1 

 

# If we are out of bounds 

263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true if right >= self.info['mjds'].size: 

right -= 1 

baseline = 1. 

266 ↛ 267line 266 didn't jump to line 267, because the condition on line 266 was never true elif left < 0: 

left += 1 

baseline = 1. 

else: 

baseline = self.info['mjds'][right] - self.info['mjds'][left] 

 

if indx is None: 

result_size = self.sb[self.filter_names[0]][left, :].size 

indx = np.arange(result_size) 

else: 

result_size = len(indx) 

# Check if we are between sunrise/set 

if baseline > self.header['timestep_max']: 

warnings.warn('Requested MJD between sunrise and sunset, returning closest maps') 

diff = np.abs(self.info['mjds'][left.max():right.max()+1]-mjd) 

closest_indx = np.array([left, right])[np.where(diff == np.min(diff))] 

airmass = self.info['airmass'][closest_indx, indx] 

mask = np.where((self.info['airmass'][closest_indx, indx].ravel() < 1.) | 

(self.info['airmass'][closest_indx, indx].ravel() > maxAM)) 

airmass = airmass.ravel() 

 

else: 

wterm = (mjd - self.info['mjds'][left])/baseline 

w1 = (1. - wterm) 

w2 = wterm 

airmass = self.info['airmass'][left, indx] * w1 + self.info['airmass'][right, indx] * w2 

mask = np.where((self.info['airmass'][left, indx] < 1.) | 

(self.info['airmass'][left, indx] > maxAM) | 

(self.info['airmass'][right, indx] < 1.) | 

(self.info['airmass'][right, indx] > maxAM)) 

airmass[mask] = badval 

 

return airmass 

 

def returnMags(self, mjd, indx=None, airmass_mask=True, planet_mask=True, 

moon_mask=True, zenith_mask=True, badval=hp.UNSEEN, 

filters=['u', 'g', 'r', 'i', 'z', 'y'], extrapolate=False): 

""" 

Return a full sky map or individual pixels for the input mjd 

 

Parameters 

---------- 

mjd : float 

Modified Julian Date to interpolate to 

indx : List of int(s) (None) 

indices to interpolate the sky values at. Returns full sky if None. If the class was 

instatiated with opsimFields, indx is the field ID, otherwise it is the healpix ID. 

airmass_mask : bool (True) 

Set high (>2.5) airmass pixels to badval. 

planet_mask : bool (True) 

Set sky maps to badval near (2 degrees) bright planets. 

moon_mask : bool (True) 

Set sky maps near (10 degrees) the moon to badval. 

zenith_mask : bool (True) 

Set sky maps at high altitude (>86.5) to badval. 

badval : float (-1.6375e30) 

Mask value. Defaults to the healpy mask value. 

filters : list 

List of strings for the filters that should be returned. 

extrapolate : bool (False) 

In indx is set, extrapolate any masked pixels to be the same as the nearest non-masked 

value from the full sky map. 

 

Returns 

------- 

sbs : dict 

A dictionary with filter names as keys and np.arrays as values which 

hold the sky brightness maps in mag/sq arcsec. 

""" 

335 ↛ 336line 335 didn't jump to line 336, because the condition on line 335 was never true if (mjd < self.loaded_range.min() or (mjd > self.loaded_range.max())): 

self._load_data(mjd) 

 

mask_rules = {'airmass': airmass_mask, 'planet': planet_mask, 

'moon': moon_mask, 'zenith': zenith_mask} 

 

left = np.searchsorted(self.info['mjds'], mjd)-1 

right = left+1 

 

# Do full sky by default 

if indx is None: 

indx = np.arange(self.sb['r'].shape[1]) 

full_sky = True 

else: 

full_sky = False 

 

# If we are out of bounds 

352 ↛ 353line 352 didn't jump to line 353, because the condition on line 352 was never true if right >= self.info['mjds'].size: 

right -= 1 

baseline = 1. 

355 ↛ 356line 355 didn't jump to line 356, because the condition on line 355 was never true elif left < 0: 

left += 1 

baseline = 1. 

else: 

baseline = self.info['mjds'][right] - self.info['mjds'][left] 

 

# Check if we are between sunrise/set 

if baseline > self.header['timestep_max']: 

warnings.warn('Requested MJD between sunrise and sunset, returning closest maps') 

diff = np.abs(self.info['mjds'][left.max():right.max()+1]-mjd) 

closest_indx = np.array([left, right])[np.where(diff == np.min(diff))].min() 

sbs = {} 

for filter_name in filters: 

sbs[filter_name] = self.sb[filter_name][closest_indx, indx] 

for mask_name in mask_rules: 

370 ↛ 369line 370 didn't jump to line 369, because the condition on line 370 was never false if mask_rules[mask_name]: 

toMask = np.where(self.info[mask_name+'_masks'][closest_indx, indx]) 

sbs[filter_name][toMask] = badval 

sbs[filter_name][np.isinf(sbs[filter_name])] = badval 

sbs[filter_name][np.where(sbs[filter_name] == hp.UNSEEN)] = badval 

else: 

wterm = (mjd - self.info['mjds'][left])/baseline 

w1 = (1. - wterm) 

w2 = wterm 

sbs = {} 

for filter_name in filters: 

sbs[filter_name] = self.sb[filter_name][left, indx] * w1 + \ 

self.sb[filter_name][right, indx] * w2 

for mask_name in mask_rules: 

if mask_rules[mask_name]: 

toMask = np.where(self.info[mask_name+'_masks'][left, indx] | 

self.info[mask_name+'_masks'][right, indx] | 

np.isinf(sbs[filter_name])) 

sbs[filter_name][toMask] = badval 

sbs[filter_name][np.where(sbs[filter_name] == hp.UNSEEN)] = badval 

sbs[filter_name][np.where(sbs[filter_name] == hp.UNSEEN)] = badval 

 

# If requested a certain pixel(s), and want to extrapolate. 

393 ↛ 394line 393 didn't jump to line 394, because the condition on line 393 was never true if (not full_sky) & extrapolate: 

masked_pix = False 

for filter_name in filters: 

if (badval in sbs[filter_name]) | (True in np.isnan(sbs[filter_name])): 

masked_pix = True 

if masked_pix: 

# We have pixels that are masked that we want reasonable values for 

full_sky_sb = self.returnMags(mjd, airmass_mask=False, planet_mask=False, moon_mask=False, 

zenith_mask=False, filters=filters) 

good = np.where((full_sky_sb[filters[0]] != badval) & ~np.isnan(full_sky_sb[filters[0]]))[0] 

ra_full = np.radians(self.header['ra'][good]) 

dec_full = np.radians(self.header['dec'][good]) 

for filtername in filters: 

full_sky_sb[filtername] = full_sky_sb[filtername][good] 

# Going to assume the masked pixels are the same in all filters 

masked_indx = np.where((sbs[filters[0]].ravel() == badval) | 

np.isnan(sbs[filters[0]].ravel()))[0] 

for i, mi in enumerate(masked_indx): 

# Note, this is going to be really slow for many pixels, should use a kdtree 

dist = _angularSeparation(np.radians(self.header['ra'][indx][i]), 

np.radians(self.header['dec'][indx][i]), 

ra_full, dec_full) 

closest = np.where(dist == dist.min())[0] 

for filtername in filters: 

sbs[filtername].ravel()[mi] = np.min(full_sky_sb[filtername][closest]) 

 

return sbs