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

1import numpy as np 

2import matplotlib.pylab as plt 

3import numpy.lib.recfunctions as rf 

4from scipy import interpolate 

5import lsst.sims.maf.metrics as metrics 

6from lsst.sims.maf.utils.snUtils import GenerateFakeObservations 

7from collections.abc import Iterable 

8import time 

9 

10__all__ = ['SNSNRMetric'] 

11 

12class SNSNRMetric(metrics.BaseMetric): 

13 

14 """ 

15 Metric to estimate the detection rate for faint supernovae (x1,color) = (-2.0,0.2) 

16 

17 Parameters 

18 ---------- 

19 list : str, opt 

20 Name of the columns used to estimate the metric 

21 Default : 'observationStartMJD', 'fieldRA', 'fieldDec','filter','fiveSigmaDepth', 

22 'visitExposureTime','night','observationId', 'numExposures','visitTime' 

23 coadd : bool, opt 

24 to make "coaddition" per night (uses snStacker) 

25 Default : True 

26 lim_sn : class, opt 

27 Reference data used to simulate LC points (interpolation) 

28 names_ref : str,opt 

29 names of the simulator used to produce reference data 

30 season : flota,opt 

31 season num 

32 Default : 1. 

33 z : float,opt 

34 redshift for this study 

35 Default : 0.01 

36 """ 

37 

38 def __init__(self, metricName='SNSNRMetric', 

39 mjdCol='observationStartMJD', RaCol='fieldRA', DecCol='fieldDec', 

40 filterCol='filter', m5Col='fiveSigmaDepth', exptimeCol='visitExposureTime', 

41 nightCol='night', obsidCol='observationId', nexpCol='numExposures', 

42 vistimeCol='visitTime', coadd=True, lim_sn=None, names_ref=None, season=1, z=0.01, **kwargs): 

43 

44 self.mjdCol = mjdCol 

45 self.m5Col = m5Col 

46 self.filterCol = filterCol 

47 self.RaCol = RaCol 

48 self.DecCol = DecCol 

49 self.exptimeCol = exptimeCol 

50 self.seasonCol = 'season' 

51 self.nightCol = nightCol 

52 self.obsidCol = obsidCol 

53 self.nexpCol = nexpCol 

54 self.vistimeCol = vistimeCol 

55 

56 cols = [self.nightCol, self.m5Col, self.filterCol, self.mjdCol, self.obsidCol, 

57 self.nexpCol, self.vistimeCol, self.exptimeCol, self.seasonCol] 

58 if coadd: 

59 cols += ['coadd'] 

60 super(SNSNRMetric, self).__init__( 

61 col=cols, metricName=metricName, **kwargs) 

62 

63 self.filterNames = np.array(['u', 'g', 'r', 'i', 'z', 'y']) 

64 self.blue_cutoff = 300. 

65 self.red_cutoff = 800. 

66 self.min_rf_phase = -20. 

67 self.max_rf_phase = 40. 

68 self.z = z 

69 self.names_ref = names_ref 

70 self.season = season 

71 

72 # SN DayMax: current date - shift days 

73 self.shift = 10. 

74 

75 # These are reference LC 

76 self.lim_sn = lim_sn 

77 

78 self.display = False 

79 

80 def run(self, dataSlice, slicePoint=None): 

81 """ 

82 run the metric 

83 

84 Parameters 

85 ---------- 

86 dataSlice : array 

87 simulation data under study 

88 

89 Returns 

90 ------- 

91 detection rate : float 

92 

93 """ 

94 time_ref = time.time() 

95 goodFilters = np.in1d(dataSlice['filter'], self.filterNames) 

96 dataSlice = dataSlice[goodFilters] 

97 if dataSlice.size == 0: 

98 return None 

99 dataSlice.sort(order=self.mjdCol) 

100 

101 if self.season != -1: 

102 seasons = self.season 

103 else: 

104 seasons = np.unique(dataSlice['season']) 

105 

106 if not isinstance(seasons, Iterable): 

107 seasons = [seasons] 

108 

109 self.info_season = None 

110 for seas in seasons: 

111 info = self.season_info(dataSlice, seas) 

112 if info is not None and info['season_length'] >= self.shift: 

113 if self.info_season is None: 

114 self.info_season = info 

115 else: 

116 self.info_season = np.concatenate((self.info_season, info)) 

117 

118 self.info_season = self.check_seasons(self.info_season) 

119 if self.info_season is None: 

120 return 0. 

121 

122 sel = dataSlice[np.in1d(dataSlice['season'], np.array(seasons))] 

123 

124 detect_frac = None 

125 if len(sel) >= 5: 

126 detect_frac = self.process(sel) 

127 

128 if detect_frac is not None: 

129 return np.median(detect_frac['frac_obs_{}'.format(self.names_ref[0])]) 

130 else: 

131 return 0. 

132 

133 def process(self, sel): 

134 """Process one season 

135 

136 Parameters 

137 --------- 

138 sel : array 

139 array of observations 

140 season : int 

141 season number 

142 

143 Returns 

144 ----- 

145 record array with the following fields: 

146 fieldRA (float) 

147 fieldDec (float) 

148 season (float) 

149 band (str) 

150 frac_obs_name_ref (float) 

151 

152 """ 

153 

154 self.band = np.unique(sel[self.filterCol])[0] 

155 time_ref = time.time() 

156 snr_obs = self.snr_slice(sel) # SNR for observations 

157 snr_fakes = self.snr_fakes(sel) # SNR for fakes 

158 detect_frac = self.detection_rate( 

159 snr_obs, snr_fakes) # Detection rate 

160 snr_obs = np.asarray(snr_obs) 

161 snr_fakes = np.asarray(snr_fakes) 

162 #self.plot(snr_obs, snr_fakes) 

163 # plt.show() 

164 detect_frac = np.asarray(detect_frac) 

165 

166 return detect_frac 

167 

168 def snr_slice(self, dataSlice, j=-1, output_q=None): 

169 """ 

170 Estimate SNR for a given dataSlice 

171 

172 Parameters 

173 --------- 

174 Input: dataSlice 

175 

176 Returns 

177 ----- 

178 array with the following fields (all are of f8 type, except band which is of U1) 

179 

180 SNR_name_ref: Signal-To-Noise Ratio estimator 

181 season : season 

182 cadence: cadence of the season 

183 season_length: length of the season 

184 MJD_min: min MJD of the season 

185 DayMax: SN max luminosity MJD (aka T0) 

186 MJD: 

187 m5_eff: mean m5 of obs passing the min_phase, max_phase cut 

188 fieldRA: mean field RA 

189 fieldDec: mean field Dec 

190 band: band 

191 m5: mean m5 (over the season) 

192 Nvisits: median number of visits (per observation) (over the season) 

193 ExposureTime: median exposure time (per observation) (over the season) 

194 

195 """ 

196 

197 # Get few infos: RA, Dec, Nvisits, m5, exptime 

198 fieldRA = np.mean(dataSlice[self.RaCol]) 

199 fieldDec = np.mean(dataSlice[self.DecCol]) 

200 # one visit = 2 exposures 

201 Nvisits = np.median(dataSlice[self.nexpCol]/2.) 

202 m5 = np.mean(dataSlice[self.m5Col]) 

203 exptime = np.median(dataSlice[self.exptimeCol]) 

204 dataSlice.sort(order=self.mjdCol) 

205 mjds = dataSlice[self.mjdCol] 

206 band = np.unique(dataSlice[self.filterCol])[0] 

207 

208 # Define MJDs to consider for metric estimation 

209 # basically: step of one day between MJDmin and MJDmax 

210 dates = None 

211 

212 for val in self.info_season: 

213 if dates is None: 

214 dates = np.arange( 

215 val['MJD_min']+self.shift, val['MJD_max']+1., 1.) 

216 else: 

217 dates = np.concatenate( 

218 (dates, np.arange(val['MJD_min']+self.shift, val['MJD_max']+1., 1.))) 

219 

220 # SN DayMax: dates-shift where shift is chosen in the input yaml file 

221 T0_lc = dates-self.shift 

222 

223 # for these DayMax, estimate the phases of LC points corresponding to the current dataSlice MJDs 

224 

225 time_for_lc = -T0_lc[:, None]+mjds 

226 

227 phase = time_for_lc/(1.+self.z) # phases of LC points 

228 # flag: select LC points only in between min_rf_phase and max_phase 

229 phase_max = self.shift/(1.+self.z) 

230 flag = (phase >= self.min_rf_phase) & (phase <= phase_max) 

231 

232 # tile m5, MJDs, and seasons to estimate all fluxes and SNR at once 

233 m5_vals = np.tile(dataSlice[self.m5Col], (len(time_for_lc), 1)) 

234 season_vals = np.tile(dataSlice[self.seasonCol], (len(time_for_lc), 1)) 

235 

236 # estimate fluxes and snr in SNR function 

237 fluxes_tot, snr = self.snr( 

238 time_for_lc, m5_vals, flag, season_vals, T0_lc) 

239 

240 # now save the results in a record array 

241 _, idx = np.unique(snr['season'], return_inverse=True) 

242 infos = self.info_season[idx] 

243 

244 vars_info = ['cadence', 'season_length', 'MJD_min'] 

245 snr = rf.append_fields( 

246 snr, vars_info, [infos[name] for name in vars_info]) 

247 snr = rf.append_fields(snr, 'DayMax', T0_lc) 

248 snr = rf.append_fields(snr, 'MJD', dates) 

249 snr = rf.append_fields(snr, 'm5_eff', np.mean( 

250 np.ma.array(m5_vals, mask=~flag), axis=1)) 

251 global_info = [(fieldRA, fieldDec, band, m5, 

252 Nvisits, exptime)]*len(snr) 

253 names = ['fieldRA', 'fieldDec', 'band', 

254 'm5', 'Nvisits', 'ExposureTime'] 

255 global_info = np.rec.fromrecords(global_info, names=names) 

256 snr = rf.append_fields( 

257 snr, names, [global_info[name] for name in names]) 

258 

259 if output_q is not None: 

260 output_q.put({j: snr}) 

261 else: 

262 return snr 

263 

264 def season_info(self, dataSlice, season): 

265 """ 

266 Get info on seasons for each dataSlice 

267 

268 Parameters 

269 ----- 

270 dataSlice : array 

271 array of observations 

272 

273 Returns 

274 ----- 

275 recordarray with the following fields: 

276 season, cadence, season_length, MJDmin, MJDmax 

277 """ 

278 

279 rv = [] 

280 

281 idx = (dataSlice[self.seasonCol] == season) 

282 slice_sel = dataSlice[idx] 

283 if len(slice_sel) < 5: 

284 return None 

285 slice_sel.sort(order=self.mjdCol) 

286 mjds_season = slice_sel[self.mjdCol] 

287 cadence = np.mean(mjds_season[1:]-mjds_season[:-1]) 

288 mjd_min = np.min(mjds_season) 

289 mjd_max = np.max(mjds_season) 

290 season_length = mjd_max-mjd_min 

291 Nvisits = np.median(slice_sel[self.nexpCol]) 

292 m5 = np.median(slice_sel[self.m5Col]) 

293 rv.append((float(season), cadence, 

294 season_length, mjd_min, mjd_max, Nvisits, m5)) 

295 

296 info_season = np.rec.fromrecords( 

297 rv, names=['season', 'cadence', 'season_length', 'MJD_min', 'MJD_max', 'Nvisits', 'm5']) 

298 

299 return info_season 

300 

301 def snr(self, time_lc, m5_vals, flag, season_vals, T0_lc): 

302 """ 

303 Estimate SNR vs time 

304 

305 Parameters 

306 ----------- 

307 time_lc : 

308 m5_vals : list(float) 

309 five-sigme depth values 

310 flag : array(bool) 

311 flag to be applied (example: selection from phase cut) 

312 season_vals : array(float) 

313 season values 

314 T0_lc : array(float) 

315 array of T0 for supernovae 

316 

317 Returns 

318 ----- 

319 fluxes_tot : list(float) 

320 list of (interpolated) fluxes 

321 snr_tab : array with the following fields: 

322 snr_name_ref (float) : Signal-to-Noise values 

323 season (float) : season num. 

324 """ 

325 

326 seasons = np.ma.array(season_vals, mask=~flag) 

327 

328 fluxes_tot = {} 

329 snr_tab = None 

330 

331 for ib, name in enumerate(self.names_ref): 

332 fluxes = self.lim_sn.fluxes[ib](time_lc) 

333 if name not in fluxes_tot.keys(): 

334 fluxes_tot[name] = fluxes 

335 else: 

336 fluxes_tot[name] = np.concatenate((fluxes_tot[name], fluxes)) 

337 

338 flux_5sigma = self.lim_sn.mag_to_flux[ib](m5_vals) 

339 snr = fluxes**2/flux_5sigma**2 

340 snr_season = 5.*np.sqrt(np.sum(snr*flag, axis=1)) 

341 

342 if snr_tab is None: 

343 snr_tab = np.asarray(np.copy(snr_season), dtype=[ 

344 ('SNR_'+name, 'f8')]) 

345 else: 

346 snr_tab = rf.append_fields( 

347 snr_tab, 'SNR_'+name, np.copy(snr_season)) 

348 """  

349 snr_tab = rf.append_fields( 

350 snr_tab, 'season', np.mean(seasons, axis=1)) 

351 """ 

352 snr_tab = rf.append_fields( 

353 snr_tab, 'season', self.get_season(T0_lc)) 

354 

355 # check if any masked value remaining 

356 # this would correspond to case where no obs point has been selected 

357 # ie no points with phase in [phase_min,phase_max] 

358 # this happens when internight gaps are large (typically larger than shift) 

359 idmask = np.where(snr_tab.mask) 

360 if len(idmask) > 0: 

361 tofill = np.copy(snr_tab['season']) 

362 season_recover = self.get_season( 

363 T0_lc[np.where(snr_tab.mask)]) 

364 tofill[idmask] = season_recover 

365 snr_tab = np.ma.filled(snr_tab, fill_value=tofill) 

366 

367 return fluxes_tot, snr_tab 

368 

369 def get_season(self, T0): 

370 """ 

371 Estimate the seasons corresponding to T0 values 

372 

373 Parameters 

374 ------- 

375 T0 : list(float) 

376 set of T0 values 

377 

378 Returns 

379 ----- 

380 list (float) of corresponding seasons 

381 """ 

382 

383 diff_min = T0[:, None]-self.info_season['MJD_min'] 

384 diff_max = -T0[:, None]+self.info_season['MJD_max'] 

385 seasons = np.tile(self.info_season['season'], (len(diff_min), 1)) 

386 flag = (diff_min >= 0) & (diff_max >= 0) 

387 seasons = np.ma.array(seasons, mask=~flag) 

388 

389 return np.mean(seasons, axis=1) 

390 

391 def snr_fakes(self, dataSlice): 

392 """ 

393 Estimate SNR for fake observations 

394 in the same way as for observations (using SNR_Season) 

395 

396 Parameters: 

397 ------- 

398 dataSlice : array 

399 array of observations 

400 

401 Returns 

402 ----- 

403 snr_tab : array with the following fields: 

404 snr_name_ref (float) : Signal-to-Noise values 

405 season (float) : season num. 

406 

407 """ 

408 

409 # generate fake observations 

410 fake_obs = None 

411 

412 # idx = (dataSlice[self.seasonCol] == season) 

413 band = np.unique(dataSlice[self.filterCol])[0] 

414 fake_obs = self.gen_fakes(dataSlice, band) 

415 

416 # estimate SNR vs MJD 

417 

418 snr_fakes = self.snr_slice( 

419 fake_obs[fake_obs['filter'] == band]) 

420 

421 return snr_fakes 

422 

423 def gen_fakes(self, slice_sel, band): 

424 """ 

425 Generate fake observations 

426 according to observing values extracted from simulations 

427 

428 Parameters 

429 ----- 

430 slice_sel : array 

431 array of observations 

432 band : str 

433 band to consider 

434 

435 Returns 

436 ----- 

437 fake_obs_season : array 

438 array of observations with the following fields 

439 observationStartMJD (float) 

440 fieldRA (float) 

441 fieldDec (float) 

442 filter (U1) 

443 fiveSigmaDepth (float) 

444 numExposures (float) 

445 visitExposureTime (float) 

446 season (int) 

447 

448 

449 """ 

450 fieldRA = np.mean(slice_sel[self.RaCol]) 

451 fieldDec = np.mean(slice_sel[self.DecCol]) 

452 Tvisit = 30. 

453 

454 fake_obs = None 

455 for val in self.info_season: 

456 cadence = val['cadence'] 

457 mjd_min = val['MJD_min'] 

458 mjd_max = val['MJD_max'] 

459 season_length = val['season_length'] 

460 Nvisits = val['Nvisits'] 

461 m5 = val['m5'] 

462 

463 # build the configuration file 

464 

465 config_fake = {} 

466 config_fake['Ra'] = fieldRA 

467 config_fake['Dec'] = fieldDec 

468 config_fake['bands'] = [band] 

469 config_fake['Cadence'] = [cadence] 

470 config_fake['MJD_min'] = [mjd_min] 

471 config_fake['season_length'] = season_length 

472 config_fake['Nvisits'] = [Nvisits] 

473 m5_nocoadd = m5-1.25*np.log10(float(Nvisits)*Tvisit/30.) 

474 config_fake['m5'] = [m5_nocoadd] 

475 config_fake['seasons'] = [val['season']] 

476 config_fake['Exposure_Time'] = [30.] 

477 config_fake['shift_days'] = 0. 

478 fake_obs_season = GenerateFakeObservations( 

479 config_fake).Observations 

480 if fake_obs is None: 

481 fake_obs = fake_obs_season 

482 else: 

483 fake_obs = np.concatenate((fake_obs, fake_obs_season)) 

484 return fake_obs 

485 

486 def plot(self, snr_obs, snr_fakes): 

487 """ Plot SNR vs time 

488 

489 Parameters 

490 ----- 

491 snr_obs : array 

492 array estimated using snr_slice(observations) 

493 

494 snr_obs : array 

495 array estimated using snr_slice(fakes) 

496 

497 """ 

498 

499 fig, ax = plt.subplots(figsize=(10, 7)) 

500 

501 title = 'season {} - {} band - z={}'.format( 

502 self.season, self.band, self.z) 

503 fig.suptitle(title) 

504 ax.plot(snr_obs['MJD'], snr_obs['SNR_{}'.format( 

505 self.names_ref[0])], label='Simulation') 

506 ax.plot(snr_fakes['MJD'], snr_fakes['SNR_{}'.format( 

507 self.names_ref[0])], ls='--', label='Fakes') 

508 

509 def PlotHistory(self, fluxes, mjd, flag, snr, T0_lc, dates): 

510 """ Plot history of Plot 

511 For each MJD, fluxes and snr are plotted 

512 Each plot may be saved as a png to make a video afterwards 

513 

514 Parameters 

515 ------ 

516 fluxes : list(float) 

517 LC fluxes 

518 mjd : list(float) 

519 mjds of the fluxes 

520 flag : array 

521 flag for selection of fluxes 

522 snr : list 

523 signal-to-noise ratio 

524 T0_lc : list(float) 

525 list of T0 supernovae 

526 dates : list(float) 

527 date of the display (mjd) 

528 

529 """ 

530 

531 dir_save = '/home/philippe/LSST/sn_metric_new/Plots' 

532 import pylab as plt 

533 plt.ion() 

534 fig, ax = plt.subplots(ncols=1, nrows=2) 

535 fig.canvas.draw() 

536 

537 colors = ['b', 'r'] 

538 myls = ['-', '--'] 

539 mfc = ['b', 'None'] 

540 tot_label = [] 

541 fontsize = 12 

542 mjd_ma = np.ma.array(mjd, mask=~flag) 

543 fluxes_ma = {} 

544 for key, val in fluxes.items(): 

545 fluxes_ma[key] = np.ma.array(val, mask=~flag) 

546 key = list(fluxes.keys())[0] 

547 jmax = len(fluxes_ma[key]) 

548 tot_label = [] 

549 tot_label_snr = [] 

550 min_flux = [] 

551 max_flux = [] 

552 

553 for j in range(jmax): 

554 

555 for ib, name in enumerate(fluxes_ma.keys()): 

556 tot_label.append(ax[0].errorbar( 

557 mjd_ma[j], fluxes_ma[name][j], marker='s', color=colors[ib], ls=myls[ib], label=name)) 

558 

559 tot_label_snr.append(ax[1].errorbar( 

560 snr['MJD'][:j], snr['SNR_'+name][:j], color=colors[ib], label=name)) 

561 fluxx = fluxes_ma[name][j] 

562 fluxx = fluxx[~fluxx.mask] 

563 if len(fluxx) >= 2: 

564 min_flux.append(np.min(fluxx)) 

565 max_flux.append(np.max(fluxx)) 

566 else: 

567 min_flux.append(0.) 

568 max_flux.append(200.) 

569 

570 min_fluxes = np.min(min_flux) 

571 max_fluxes = np.max(max_flux) 

572 

573 tot_label.append(ax[0].errorbar([T0_lc[j], T0_lc[j]], [ 

574 min_fluxes, max_fluxes], color='k', label='DayMax')) 

575 tot_label.append(ax[0].errorbar([dates[j], dates[j]], [ 

576 min_fluxes, max_fluxes], color='k', ls='--', label='Current MJD')) 

577 fig.canvas.flush_events() 

578 # plt.savefig('{}/{}_{}.png'.format(dir_save, 'snr', 1000 + j)) 

579 if j != jmax-1: 

580 ax[0].clear() 

581 tot_label = [] 

582 tot_label_snr = [] 

583 

584 labs = [l.get_label() for l in tot_label] 

585 ax[0].legend(tot_label, labs, ncol=1, loc='best', 

586 prop={'size': fontsize}, frameon=False) 

587 ax[0].set_ylabel('Flux [e.sec$^{-1}$]', fontsize=fontsize) 

588 

589 ax[1].set_xlabel('MJD', fontsize=fontsize) 

590 ax[1].set_ylabel('SNR', fontsize=fontsize) 

591 ax[1].legend() 

592 labs = [l.get_label() for l in tot_label_snr] 

593 ax[1].legend(tot_label_snr, labs, ncol=1, loc='best', 

594 prop={'size': fontsize}, frameon=False) 

595 for i in range(2): 

596 ax[i].tick_params(axis='x', labelsize=fontsize) 

597 ax[i].tick_params(axis='y', labelsize=fontsize) 

598 

599 def detection_rate(self, snr_obs, snr_fakes): 

600 """ 

601 Estimate the time fraction(per season) for which 

602 snr_obs > snr_fakes = detection rate 

603 For regular cadences one should get a result close to 1 

604 

605 Parameters 

606 ------- 

607 snr_obs : array 

608 array estimated using snr_slice(observations) 

609 

610 snr_fakes: array 

611 array estimated using snr_slice(fakes) 

612 

613 Returns 

614 ----- 

615 record array with the following fields: 

616 fieldRA (float) 

617 fieldDec (float) 

618 season (float) 

619 band (str) 

620 frac_obs_name_ref (float) 

621 """ 

622 

623 ra = np.mean(snr_obs['fieldRA']) 

624 dec = np.mean(snr_obs['fieldDec']) 

625 band = np.unique(snr_obs['band'])[0] 

626 

627 rtot = [] 

628 

629 for season in np.unique(snr_obs['season']): 

630 idx = snr_obs['season'] == season 

631 sel_obs = snr_obs[idx] 

632 idxb = snr_fakes['season'] == season 

633 sel_fakes = snr_fakes[idxb] 

634 

635 sel_obs.sort(order='MJD') 

636 sel_fakes.sort(order='MJD') 

637 r = [ra, dec, season, band] 

638 names = [self.RaCol, self.DecCol, 'season', 'band'] 

639 for sim in self.names_ref: 

640 fakes = interpolate.interp1d( 

641 sel_fakes['MJD'], sel_fakes['SNR_'+sim]) 

642 obs = interpolate.interp1d(sel_obs['MJD'], sel_obs['SNR_'+sim]) 

643 mjd_min = np.max( 

644 [np.min(sel_obs['MJD']), np.min(sel_fakes['MJD'])]) 

645 mjd_max = np.min( 

646 [np.max(sel_obs['MJD']), np.max(sel_fakes['MJD'])]) 

647 mjd = np.arange(mjd_min, mjd_max, 1.) 

648 

649 diff_res = obs(mjd)-fakes(mjd) 

650 

651 idx = diff_res >= 0 

652 r += [len(diff_res[idx])/len(diff_res)] 

653 names += ['frac_obs_'+sim] 

654 rtot.append(tuple(r)) 

655 

656 return np.rec.fromrecords(rtot, names=names) 

657 

658 def check_seasons(self, tab): 

659 """ Check wether seasons have no overlap 

660 if it is the case: modify MJD_min and season length of the corresponding season 

661 return only seasons with season_length > 30 days 

662 

663 Parameters 

664 -------------- 

665 tab : array with the following fields: 

666 

667 

668 Returns 

669 --------- 

670 tab : array with the following fields: 

671 """ 

672 if tab is None or len(tab) == 1: 

673 return tab 

674 

675 if len(tab) > 1: 

676 diff = tab['MJD_min'][1:]-tab['MJD_max'][:-1] 

677 idb = np.argwhere(diff < 20.) 

678 if len(idb) >= 1: 

679 tab['MJD_min'][idb+1] = tab['MJD_max'][idb]+20. 

680 tab['season_length'][idb+1] = tab['MJD_max'][idb+1] - \ 

681 tab['MJD_min'][idb+1] 

682 

683 return tab[tab['season_length'] > 30.]