1
2 '''Spectractor submodule containing functions for managing various files,
3 journals, creating logs etc.
4
5 @author: Dmitry Nasonov
6 '''
7
8 import re
9 import os, sys, shutil, glob
10 import cPickle
11 import collections
12
13 from spectractor import get_wv_range
14 data_dir, out_dir = 'data', 'out'
15
16
18 """Fill dictionary which values are lists."""
19 try:
20 if val not in indct[key]:
21 indct[key].append(val)
22 except KeyError:
23 indct[key] = [val, ]
24 except TypeError:
25 print >> sys.stderr, "TypeError in filler! Key, value:", key, val
26 sys.exit(True)
27
28
30 """Fill dictionary which values are sets."""
31 try:
32 indct[key].add(val)
33 except KeyError:
34 indct[key] = set([val])
35
36
38 """Backup file if it is exist: move it with '-backup' postfix."""
39 if os.path.isfile(file_pth):
40 shutil.move(file_pth, file_pth + '-backup')
41 if verbose:
42 print "Move", file_pth, "to", file_pth + '-backup'
43
44
45 -def rexper(regexp, path, reflag=None, splitnig=False):
46 """Get parameters from file name using regexp.
47 Parameters are: int ID (for example, night number), flag.
48 For now, regexp.findall must return 3 values! If not, return (None, None).
49 @param regexp: regular expression describing file name
50 @param path: path to file
51 @param reflag: tuple of two values. If not empty, flag containing in first
52 value will be replaced to second one
53 @param splitnig: split ID to tuple of 'night' and spectrum number
54 @return: ID (night number): int or tuple of ints with splitnig, flag
55 """
56 try:
57 bname = os.path.basename(path)
58 night, img, flag = regexp.findall(bname)[0]
59 except (IndexError, ValueError):
60 print >> sys.stderr, "Filename", bname, "does not match regexp!"
61 return None, None
62 if reflag and flag in reflag[0]:
63 flag = reflag[1]
64 if not splitnig:
65 night = int(night + img.zfill(3))
66 else:
67 night = (int(night), int(img))
68 return night, flag
69
70
72 """Directory walker: search files and dirs in args matching regexp.
73 @param exts: set of file extensions for matching
74 @param namrxp: filename regular expression, but without extension
75 @param args: list of paths to files and dirs
76 """
77 - def __init__(self, args, exts=("200", "fds"), namrxp=".*", verbose=False):
78 self.files_dct = {}
79 self.workdirs = []
80 self.path_set, self.spath_set = set(), set()
81 self.verbose = verbose
82
83 self.rexp_spec = "^[a-z_]{0,3}(\d{3})(\d{1,3})[-_]?([a-z_\-]*).*\."
84 self.fdsrexp = re.compile(self.rexp_spec+'fds'+'$', re.I | re.U)
85
86
87 self.rexp_mt = \
88 "^[lns](\d{1})([A-C0-9])(\d{2})[_-]?(\d{2,3})([a-z_]?).*"
89 self.rexp_fits = \
90 "^(?:Bn)?(\d{4})(\d{2})(\d{2})[_-]?(\d{3})([spbfdot_]?).*"
91 exts = "|".join(exts)
92 self.rxp = re.compile(namrxp+'\.('+exts+')$', re.I | re.U)
93
94 for arg in args:
95 if os.path.isdir(arg):
96 self.workdirs.append(arg)
97 if verbose:
98 print "Appending", arg, "to the list of working dirs"
99 elif os.path.isdir(os.path.join(os.getcwd(), arg)):
100 self.workdirs.append(os.path.join(os.getcwd(), arg))
101 if verbose:
102 print "Appending", os.path.join(os.getcwd(), arg), \
103 "to the list of working dirs"
104 elif os.path.isfile(arg) and len(self.rxp.findall(arg)):
105 filler(self.files_dct, self.rxp.findall(arg)[0], arg)
106 if verbose:
107 print "Appending", arg, "to the list of working files"
108 elif verbose:
109 print arg, "is not a directory or needed file, continue."
110
112 """Walk on workdirs, collect subdirectories.
113 @return: list of subdirectories
114 """
115 if not self.workdirs:
116 print "Sorry, no dirs for walk."
117 return
118 subdirs = []
119 for dirw in self.workdirs:
120
121 for root, dirs, files in os.walk(dirw):
122 for wdir in sorted(dirs):
123 subdirs.append(os.path.join(root, wdir))
124 return subdirs
125
126 - def walk(self, exclude="excluded"):
127 """Walk into working dirs, search some kind of files.
128 @param exclude: if it in file path, exclude file. Useful when
129 we want to exclude some subdir
130 """
131 if not self.workdirs:
132 if not self.files_dct:
133 print "Sorry, no files found."
134 return
135 for dirw in self.workdirs:
136
137 for root, dirs, files in os.walk(dirw):
138
139 for wfile in sorted(files):
140 if len(self.rxp.findall(wfile)) and exclude not in root:
141 filler(self.files_dct, self.rxp.findall(wfile)[0],
142 os.path.join(root, wfile))
143
144 - def resolver(self, ext='rv', filtr=None, reflag=None, splitnig=False):
145 """Select data, get ID and 'flag' from filename, collect paths.
146 Here we assume, that file name contains information about ID
147 (night number, number of spectrum) and flag. Others will be broken
148 (or you must change default regular expression).
149 @param ext: select file type, must be file extension
150 @param filtr: selecting only given flags, exclude others. Select all
151 when filtr in ("all", None)
152 @param reflag: tuple of two values. If not empty, flag containing in
153 first value will be replaced to second one
154 @param splitnig: split ID to tuple of 'night' and spectrum number
155 @return: list of tuples with 'night', flag and path to file
156 """
157 try:
158 files = self.files_dct[ext]
159 except KeyError:
160 print >> sys.stderr, "Sorry,", ext, "files seem not to exist."
161 return self.path_set
162 rexp = re.compile(self.rexp_spec + ext + '$', re.I | re.U)
163 for path in files:
164 night, flag = rexper(rexp, path, reflag=reflag, splitnig=splitnig)
165
166 if filtr in ("all", None) or flag in filtr:
167 self.path_set.add((night, flag, path))
168 elif self.verbose:
169 print "Exclude", night, path
170 if not len(self.path_set):
171 print >> sys.stderr, "No", ext, "files in path!"
172 return self.path_set
173
174 - def select_spectra(self, journal, ext='fits', iflag=None, splitnig=False):
175 """Select data, get ID ('night') and flag from filename, collect paths.
176 Here we assume, that file name contains information about ID
177 (night number, number of spectrum) and flag. Others will be broken
178 (or you must change default regular expression). Then we check presence
179 of ID in journal keys, get spectrum parameters from journal and append
180 this data to resulting list.
181 @param journal: journal dictionary with spectra IDs as keys and spectra
182 parameters (e.g. JYear, MJD, spectrograph key, limiting
183 wavelengths, S/N etc) as values
184 @param ext: select file type, must be file extension
185 @param iflag: Flags to select. Can be a string, if flag is one symbol
186 @param splitnig: split 'night' to tuple of night and spectrum number
187 @return: list of tuples with ID ('night'), flag, path to file and
188 other parameters. Currently parameters are fds path, dictionary
189 of points, defining the flatten polynome, heliocentric
190 correction and S/N value
191 """
192 try:
193 files = self.files_dct[ext]
194 except KeyError:
195 print >> sys.stderr, "Sorry,", ext, "files seem not to exist"
196 return self.spath_set
197 rexp = re.compile(self.rexp_spec + ext + '$', re.I | re.U)
198 if not journal:
199 self.journal = self.mk_dummy_journal(rexp, files)
200 else:
201 self.journal = journal
202 Sp = collections.namedtuple("Sp", "id flag pth fdpth plpth Vr sn jy jd")
203 for path in files:
204 night, flag = rexper(rexp, path, reflag=False, splitnig=splitnig)
205 if flag and iflag and flag not in iflag:
206 if self.verbose:
207 print "Exclude", night, "with flag", flag
208 continue
209 if night not in self.journal:
210 continue
211 jy, mjd, spc, lwv, hwv, Vr, sn, fdsnam = self.journal[night][:8]
212
213 pls_pth = os.path.splitext(path)[0]+'.ccm.txt'
214 if not os.path.isfile(pls_pth):
215 print >> sys.stderr, "Exclude", path+": no ccm.txt file"
216 continue
217 fds_pth = os.path.join(os.path.dirname(path), str(fdsnam)+'.fds')
218 if not os.path.isfile(fds_pth):
219 fds_pth = self.find_fds(path, str(night).zfill(6))
220 if not fds_pth:
221 print >> sys.stderr, "Exclude", path+": no fds file"
222 continue
223 if self.verbose:
224 print "Append", night, flag, "with shift", Vr
225 params = Sp(night, flag, path, fds_pth, pls_pth, Vr, sn, jy, mjd)
226 self.spath_set.add(params)
227 return self.spath_set
228
230 """Make dummy journal with paths to fds and wavelength limits
231 """
232 journal = {}
233 for path in files:
234 night, flag = rexper(rexp, path, reflag=False, splitnig=False)
235 if night not in journal:
236 fds_pth = self.find_fds(path, str(night).zfill(6))
237 if not fds_pth:
238 print "Exclude", path, "no fds"
239 continue
240 lwv, hwv = get_wv_range(fds_pth)
241 fds_pth = os.path.basename(fds_pth)
242
243 journal[night] = [0, 0, '', lwv, hwv, 0, 1, fds_pth]
244 return journal
245
247 """Try to find fds, using path to file with spectrum.
248 We assume that fds and spectrum name are similar and search fds
249 given at the same night, that of spectrum.
250 """
251 fdses = {}
252 nig, num = night[:3], int(night[3:])
253 rxp = '*'+nig+'*'+'.fds'
254 if os.path.dirname(path):
255 rxp = os.path.dirname(path) + os.sep + rxp
256 fds_pths = glob.glob(rxp)
257 if not fds_pths:
258 return
259 for fdpth in fds_pths:
260 fdnam = os.path.basename(fdpth)
261 night, img, flag = self.fdsrexp.findall(fdnam)[0]
262 if img:
263 fdses[abs(num-int(img))] = fdpth
264 return fdses[min(fdses)]
265
267 """Select image files with filenames matching rexp_fits or rexp_mt.
268 Currently only fits, fts and mt file extensions are supported.
269 """
270 rexpfts = re.compile(self.rexp_fits, re.I | re.U)
271 rexpmt = re.compile(self.rexp_mt, re.I | re.U)
272 Sp = collections.namedtuple("Sp", "pth num date flag keymode")
273 for ext in self.files_dct:
274 for pth in self.files_dct[ext]:
275 fnam = os.path.basename(pth)
276 if ext.lower() in ("fits", "fts") \
277 and len(rexpfts.findall(fnam)):
278 year, mon, day, num, flag = rexpfts.findall(fnam)[0]
279 elif ext.lower() == "mt" and len(rexpmt.findall(fnam)):
280 year, mon, day, num, flag = rexpmt.findall(fnam)[0]
281 mon = str(int(mon, 16)).zfill(2)
282 year = str(2000 + int(year)) if int(year) else "2010"
283 else:
284 continue
285 keymode = False
286
287
288 if 20080422 < int("".join((year, mon, day))) < 20081103:
289 keymode = "flstr"
290 date = tuple(map(int, (year, mon, day)))
291 params = Sp(pth, num, date, flag, keymode)
292 self.path_set.add(params)
293 return self.path_set
294
295
296 -def read_journal(j_pth): return cPickle.load(open(j_pth, 'rb'))
297
298
299 -def mod_journal(journal, sn=10, rvc=True, rvcorr_dct=None, instr=None,
300 lams=None, jd=None, verbose=False):
301 """Exclude or correct items in journal depending on input parameters.
302 @param journal: dictionary with spectra main parameters
303 @param sn: limiting signal-to-noise ratio value: exclude all spectra with
304 lower S/N
305 @param rvc: Set Va shift to zero. Useful for telluric line measurements
306 @param rvcorr_dct: Dictionary of heliocentric velocity corrections,
307 defined, for example, by telluric lines measurements
308 @param instr: Select spectra obtained with given spectrograph
309 @param lams: Exclude all spectra, which no contain given vawelength
310 @param jd: Exclude all spectra, which obtained earlier, than given JYear
311 value
312 """
313 if not rvc:
314 print "Set Va to zero"
315 for nig, dat in journal.items():
316 jy, mjd, spc, lwv, hwv, Vr, jsn, fn = dat[:8]
317 if sn and jsn < sn:
318 if verbose:
319 print "Exclude", nig, "S/N", sn
320 del journal[nig]
321 continue
322 if instr and spc not in instr:
323 if verbose:
324 print "Exclude", nig, "instr", instr
325 del journal[nig]
326 continue
327 if lams and (lwv > lams[1] or hwv < lams[0]):
328 if verbose:
329 print "Exclude", nig, "wv limits", lwv, hwv
330 del journal[nig]
331 continue
332 if jd and jy < jd:
333 if verbose:
334 print "Exclude", nig, "JD", jd
335 del journal[nig]
336 continue
337 if not rvc:
338 if type(journal[nig]) is not list:
339 journal[nig] = list(journal[nig])
340 journal[nig][5] = 0
341 if rvcorr_dct and nig in rvcorr_dct:
342 if type(journal[nig]) is not list:
343 journal[nig] = list(journal[nig])
344 journal[nig][5] += rvcorr_dct[nig]
345
346
348 """Convert float value to tuple of (int h, int m, float s).
349 Useful for coordinates and times.
350 @param divc: division coefficient for val
351 """
352 h, rest = divmod(val/divc, 1)
353 m, rest = divmod(rest * 60, 1)
354 s = round(rest * 60, 2)
355 return int(h), int(m), s
356
357
359 """Extract information from FITS headers of given FITS files, create log
360 @param fitsfixmode: when FIX header is incorrect, try to fix it with given
361 option. Can be 'fix' and 'silentfix'
362 """
363 - def __init__(self, fitsfixmode='silentfix', verbose=False):
364 self.fitsfixmode = fitsfixmode
365 self.verbose = verbose
366
367 self.stdkeys = ['AUTHOR', 'REFERENC', 'DATE', 'ORIGIN', 'DATE-OBS',
368 'TELESCOP', 'INSTRUME', 'OBSERVER', 'OBJECT', 'EQUINOX', 'EPOCH']
369 self.nonstdkeys = ['OBSERVAT', 'CHIPID', 'DETNAME', 'CCDTEMP',
370 'P_DEWAR', 'SEEING', 'PROG-ID']
371
372
373 self.journal_dct = {}
374 self.obs_dct = {}
375 self.key_dct = {"bias": 'b', "thar": 't', "flatfield": 'f', "ff": 'f',
376 "ll": 'f', "sky": 's', "ref.ima.": 'r'}
377 self.postfix, self.ext = '.', 'fits'
378 self.print_str = "[01;31m%s not found in header! Set it to zero.[0m"
379 self.keyset = lambda key: self.key_dct.get(key.lower()) \
380 if self.key_dct.get(key.lower()) else 'o'
381 self.trygetkey = lambda head, *args: filter(head.has_key, args)
382 self.seper = lambda fstr, h, m, s: \
383 "%s" % ":".join(["%02d" % h, "%02d" % m, fstr % s])
384 self.biases, self.flats, self.calib = {}, {}, {}
385
386 - def modkey(self, val, keymode=False):
387 """Modify input value, depending on keymode.
388 Because of different standards of writing FITS headers in SAO RAS,
389 we need to extract the right value. For example, val maybe string,
390 float(string) or int(float * const).
391 @param keymode: False (None), 'flstr' or 'str'. Define, how we can
392 extract correct information from given value
393 """
394 if val and not keymode and 3000 < abs(val) < 1250000:
395 val = val / 3600.
396 elif keymode == "flstr":
397 val = str(val)
398 val = float(val[-4:])/3600 + int(val[-6:-4])/60. + int(val[:-6])
399 elif keymode == "str":
400 if type(val) is str and " " in val:
401 val = val.split()
402 elif type(val) is str and ":" in val:
403 val = val.split(":")
404 val = float(val[2])/3600 + int(val[1])/60. + int(val[0])
405 return val
406
407 - def pickhead(self, head, keymode, *args):
408 """Look into FITS header for existance of keys, return head[key].
409 First, we look on existence of keys, containing in args.
410 Second, we get existing value from header or return zero.
411 """
412 key = self.trygetkey(head, *args)
413 if key:
414 val = head.get(key[0])
415 else:
416 print self.print_str % " ".join(args)
417 return 0
418 if keymode == "raw":
419 return val
420 else:
421 return self.modkey(val, keymode=keymode)
422
424 ra = self.pickhead(head, keymode, 'RA')
425 if not keymode:
426 ra = ra / 15.
427 dec = self.pickhead(head, keymode, 'DEC')
428 az = self.pickhead(head, keymode, 'A_PNT', 'AZIMUTH')
429 zen = self.pickhead(head, keymode, 'Z_PNT', 'ZDIST', 'ZENDIST')
430 if type(az) in (int, float) and az < 0:
431 az += 360
432
433 ra, dec = clocker(ra), clocker(dec)
434 az, zen = clocker(az), clocker(zen)
435 return ra, dec, az, zen
436
437 - def logger(self, curfts, num, date, file_pth, keymode=False):
438 """Create an observation log for input images.
439 Search for some specific keys in given fits header and write it
440 to 'journal': journal_dct and obs_dct dictionaries.
441 """
442
443 try:
444 ra, dec, az, zen = self.getcoords(curfts[0].header, keymode=keymode)
445 except (ValueError, TypeError, AttributeError):
446 print "Trying to fix", file_pth, "FITS header."
447 curfts.verify(self.fitsfixmode)
448 keymode = "str"
449 ra, dec, az, zen = self.getcoords(curfts[0].header, keymode=keymode)
450 head = curfts[0].header
451
452
453
454 wind = self.pickhead(head, 'raw', 'WIND')
455
456 moscowtime_start = self.pickhead(head, 'raw', 'MT') / 3600.
457 focus = self.pickhead(head, 'raw', 'FOCUS')
458 t_out = self.pickhead(head, 'raw', 'T_OUT')
459 t_in = self.pickhead(head, 'raw', 'T_IN')
460 t_mirr = self.pickhead(head, 'raw', 'T_MIRR')
461 pres = self.pickhead(head, 'raw', 'PRES')
462
463 hum = self.pickhead(head, 'raw', 'HUMD')
464
465 jd = head.get('JD')
466
467 self.journal_dct['KEYMODE'] = [keymode]
468 for key in (self.nonstdkeys + self.stdkeys):
469 if key not in self.journal_dct and head.get(key):
470 self.journal_dct[key] = [head.get(key),]
471 elif head.get(key) and head.get(key) not in self.journal_dct[key]:
472 self.journal_dct[key].append(head.get(key))
473 elif key not in self.journal_dct and not head.get(key):
474 self.journal_dct[key] = []
475
476 time_end = self.pickhead(head, 'raw', 'TM_END') / 3600.
477 try:
478 time_start = self.pickhead(head, 'raw', 'TM-START',
479 'TM_START') / 3600.
480 time = clocker(time_start)
481 except TypeError:
482 time = self.pickhead(head, 'raw', 'TM-START', 'TM_START')
483 exp = int(self.pickhead(head, 'raw', 'EXPTIME'))
484 obj = self.pickhead(head, 'raw', 'OBJECT')
485 flag = self.keyset(obj)
486 if flag == "b" and exp != 0:
487 flag = "o"
488 elif exp == 0 and flag != "b":
489 flag, obj = "b", "BIAS"
490
491 if flag == "b":
492 newname = "".join(('b', num, self.postfix, self.ext))
493 self.biases[file_pth] = newname
494 elif flag == "f":
495 newname = "".join(('f', num, self.postfix, self.ext))
496 self.flats[file_pth] = newname
497 elif flag == "t":
498 newname = "".join(('l', num, "t", self.postfix, self.ext))
499 self.calib[file_pth] = newname
500
501 dateobs = head.get('DATE-OBS')
502 if self.verbose:
503 self.warner(exp, obj, num)
504 self.obs_dct[int(num)] = [date, flag, obj, time, exp, ra, dec,
505 az, zen, dateobs]
506
507 - def warner(self, exp, obj, num):
508 """Warn about some problems."""
509 if exp == 0 and str(obj).lower() != "bias":
510 print "Wrong object name:", obj, "with exp=0 in spectrum", num
511
512
513
515 log = []
516 for num, val in sorted(self.obs_dct.items()):
517 log.append(self.logstringer(num, val))
518 return log
519
521 jrn = []
522 for key, val in sorted(self.journal_dct.items()):
523 if not val or val == [" "]:
524 continue
525 elif key in ("CCDTEMP", "P_DEWAR") and val:
526 val = [str(sum(val)/len(val))]
527 elif key in ("DATE-OBS", "ORIGIN", "AUTHOR", "OBSERVER",
528 "SEEING") and val:
529 val = [", ".join(map(str, val))]
530 jrn.append("%8s %s" % (key, " ".join(map(str, val))))
531 return jrn
532
534 """Make a string for given parameters of observation log."""
535 date, flag, obj, time, exp, ra, dec, az, zen, dateobs = val
536 date = "".join((str(date[0]), str(date[1]).zfill(2),
537 str(date[2]).zfill(2)))+" "+flag
538
539 time = self.seper("%02d", *time)
540 ra = self.seper("%04.1f", *ra)
541 dec = self.seper("%04.1f", *dec)
542 az = self.seper("%04.1f", *az)
543 zen = self.seper("%04.1f", *zen)
544 return "%3d %10s %15s %8s %4d %11s %11s %11s %10s %s" % (int(num),
545 date, obj, time, exp, ra, dec, az, zen, dateobs)
546