Project

General

Profile

Revision 97d86a8f

Added by Alessandro_N about 9 years ago

Python script to edit and merge FITS catalogues.

View differences:

common/table_creation/astCoords.py
1
"""module for coordinate manipulation (conversions, calculations etc.)
2

  
3
(c) 2007-2012 Matt Hilton
4

  
5
(c) 2013 Matt Hilton & Steven Boada
6

  
7
U{http://astlib.sourceforge.net}
8

  
9
"""
10

  
11
import sys
12
import math
13
import numpy
14
import string
15
from PyWCSTools import wcscon
16

  
17
#-----------------------------------------------------------------------------
18
def hms2decimal(RAString, delimiter):
19
    """Converts a delimited string of Hours:Minutes:Seconds format into decimal
20
    degrees.
21

  
22
    @type RAString: string
23
    @param RAString: coordinate string in H:M:S format
24
    @type delimiter: string
25
    @param delimiter: delimiter character in RAString
26
    @rtype: float
27
    @return: coordinate in decimal degrees
28

  
29
    """
30
    # is it in HH:MM:SS format?
31
    if delimiter == "":
32
        RABits = str(RAString).split()
33
    else:
34
        RABits = str(RAString).split(delimiter)
35
    if len(RABits) > 1:
36
        RAHDecimal = float(RABits[0])
37
        if len(RABits) > 1:
38
            RAHDecimal = RAHDecimal+(float(RABits[1])/60.0)
39
        if len(RABits) > 2:
40
            RAHDecimal = RAHDecimal+(float(RABits[2])/3600.0)
41
        RADeg = (RAHDecimal/24.0)*360.0
42
    else:
43
        RADeg = float(RAString)
44

  
45
    return RADeg
46

  
47
#-----------------------------------------------------------------------------
48
def dms2decimal(decString, delimiter):
49
    """Converts a delimited string of Degrees:Minutes:Seconds format into
50
    decimal degrees.
51

  
52
    @type decString: string
53
    @param decString: coordinate string in D:M:S format
54
    @type delimiter: string
55
    @param delimiter: delimiter character in decString
56
    @rtype: float
57
    @return: coordinate in decimal degrees
58

  
59
    """
60
    # is it in DD:MM:SS format?
61
    if delimiter == "":
62
        decBits = str(decString).split()
63
    else:
64
        decBits = str(decString).split(delimiter)
65
    if len(decBits) > 1:
66
        decDeg = float(decBits[0])
67
        if decBits[0].find("-") != -1:
68
            if len(decBits) > 1:
69
                decDeg = decDeg-(float(decBits[1])/60.0)
70
            if len(decBits) > 2:
71
                decDeg = decDeg-(float(decBits[2])/3600.0)
72
        else:
73
            if len(decBits) > 1:
74
                decDeg = decDeg+(float(decBits[1])/60.0)
75
            if len(decBits) > 2:
76
                decDeg = decDeg+(float(decBits[2])/3600.0)
77
    else:
78
        decDeg = float(decString)
79

  
80
    return decDeg
81

  
82
#-----------------------------------------------------------------------------
83
def decimal2hms(RADeg, delimiter):
84
    """Converts decimal degrees to string in Hours:Minutes:Seconds format with
85
    user specified delimiter.
86

  
87
    @type RADeg: float
88
    @param RADeg: coordinate in decimal degrees
89
    @type delimiter: string
90
    @param delimiter: delimiter character in returned string
91
    @rtype: string
92
    @return: coordinate string in H:M:S format
93

  
94
    """
95
    hours = (RADeg/360.0)*24
96
    #if hours < 10 and hours >= 1:
97
    if 1 <= hours < 10:
98
        sHours = "0"+str(hours)[0]
99
    elif hours >= 10:
100
        sHours = str(hours)[:2]
101
    elif hours < 1:
102
        sHours = "00"
103

  
104
    if str(hours).find(".") == -1:
105
        mins = float(hours)*60.0
106
    else:
107
        mins = float(str(hours)[str(hours).index("."):])*60.0
108
    #if mins<10 and mins>=1:
109
    if 1 <= mins<10:
110
        sMins = "0"+str(mins)[:1]
111
    elif mins >= 10:
112
        sMins = str(mins)[:2]
113
    elif mins < 1:
114
        sMins = "00"
115

  
116
    secs = (hours-(float(sHours)+float(sMins)/60.0))*3600.0
117
    #if secs < 10 and secs>0.001:
118
    if 0.001 < secs < 10:
119
        sSecs = "0"+str(secs)[:str(secs).find(".")+4]
120
    elif secs < 0.0001:
121
        sSecs = "00.001"
122
    else:
123
        sSecs = str(secs)[:str(secs).find(".")+4]
124
    if len(sSecs) < 5:
125
        sSecs = sSecs+"00"	# So all to 3dp
126

  
127
    if float(sSecs) == 60.000:
128
        sSecs = "00.00"
129
        sMins = str(int(sMins)+1)
130
    if int(sMins) == 60:
131
        sMins = "00"
132
        sDeg = str(int(sDeg)+1)
133

  
134
    return sHours+delimiter+sMins+delimiter+sSecs
135

  
136
#------------------------------------------------------------------------------
137
def decimal2dms(decDeg, delimiter):
138
    """Converts decimal degrees to string in Degrees:Minutes:Seconds format
139
    with user specified delimiter.
140

  
141
    @type decDeg: float
142
    @param decDeg: coordinate in decimal degrees
143
    @type delimiter: string
144
    @param delimiter: delimiter character in returned string
145
    @rtype: string
146
    @return: coordinate string in D:M:S format
147

  
148
    """
149
    # Positive
150
    if decDeg > 0:
151
        #if decDeg < 10 and decDeg>=1:
152
        if 1 <= decDeg < 10:
153
            sDeg = "0"+str(decDeg)[0]
154
        elif decDeg >= 10:
155
            sDeg = str(decDeg)[:2]
156
        elif decDeg < 1:
157
            sDeg = "00"
158

  
159
        if str(decDeg).find(".") == -1:
160
            mins = float(decDeg)*60.0
161
        else:
162
            mins = float(str(decDeg)[str(decDeg).index("."):])*60
163
        #if mins<10 and mins>=1:
164
        if 1 <= mins < 10:
165
            sMins = "0"+str(mins)[:1]
166
        elif mins >= 10:
167
            sMins = str(mins)[:2]
168
        elif mins < 1:
169
            sMins = "00"
170

  
171
        secs = (decDeg-(float(sDeg)+float(sMins)/60.0))*3600.0
172
        #if secs<10 and secs>0:
173
        if 0 < secs < 10:
174
            sSecs = "0"+str(secs)[:str(secs).find(".")+3]
175
        elif secs < 0.001:
176
            sSecs = "00.00"
177
        else:
178
            sSecs = str(secs)[:str(secs).find(".")+3]
179
        if len(sSecs) < 5:
180
            sSecs = sSecs+"0"	# So all to 2dp
181

  
182
        if float(sSecs) == 60.00:
183
            sSecs = "00.00"
184
            sMins = str(int(sMins)+1)
185
        if int(sMins) == 60:
186
            sMins = "00"
187
            sDeg = str(int(sDeg)+1)
188

  
189
        return "+"+sDeg+delimiter+sMins+delimiter+sSecs
190

  
191
    else:
192
        #if decDeg>-10 and decDeg<=-1:
193
        if -10 < decDeg <= -1:
194
            sDeg = "-0"+str(decDeg)[1]
195
        elif decDeg <= -10:
196
            sDeg = str(decDeg)[:3]
197
        elif decDeg > -1:
198
            sDeg = "-00"
199

  
200
        if str(decDeg).find(".") == -1:
201
            mins = float(decDeg)*-60.0
202
        else:
203
            mins = float(str(decDeg)[str(decDeg).index("."):])*60
204
        #if mins<10 and mins>=1:
205
        if 1 <= mins < 10:
206
            sMins = "0"+str(mins)[:1]
207
        elif mins >= 10:
208
            sMins = str(mins)[:2]
209
        elif mins < 1:
210
            sMins = "00"
211

  
212
        secs = (decDeg-(float(sDeg)-float(sMins)/60.0))*3600.0
213
        #if secs>-10 and secs<0:
214
        # so don't get minus sign
215
        if -10 < secs < 0:
216
            sSecs = "0"+str(secs)[1:str(secs).find(".")+3]
217
        elif secs > -0.001:
218
            sSecs = "00.00"
219
        else:
220
            sSecs = str(secs)[1:str(secs).find(".")+3]
221
        if len(sSecs) < 5:
222
            sSecs = sSecs+"0"	# So all to 2dp
223

  
224
        if float(sSecs) == 60.00:
225
            sSecs = "00.00"
226
            sMins = str(int(sMins)+1)
227
        if int(sMins) == 60:
228
            sMins = "00"
229
            sDeg = str(int(sDeg)-1)
230

  
231
        return sDeg+delimiter+sMins+delimiter+sSecs
232

  
233
#-----------------------------------------------------------------------------
234
def calcAngSepDeg(RA1, dec1, RA2, dec2):
235
    """Calculates the angular separation of two positions on the sky (specified
236
    in decimal degrees) in decimal degrees, assuming a tangent plane projection
237
    (so separation has to be <90 deg). Note that RADeg2, decDeg2 can be numpy
238
    arrays.
239

  
240
    @type RADeg1: float
241
    @param RADeg1: R.A. in decimal degrees for position 1
242
    @type decDeg1: float
243
    @param decDeg1: dec. in decimal degrees for position 1
244
    @type RADeg2: float or numpy array
245
    @param RADeg2: R.A. in decimal degrees for position 2
246
    @type decDeg2: float or numpy array
247
    @param decDeg2: dec. in decimal degrees for position 2
248
    @rtype: float or numpy array, depending upon type of RADeg2, decDeg2
249
    @return: angular separation in decimal degrees
250

  
251
    ***** Updates added by Alessandro Nastasi - 26/06/2014 *****
252

  
253
    Now RA and DEC can be entered indifferently as decimal degrees or as 
254
    hh:mm:ss.s or hh mm ss.s (provided that they are passed as STRING
255
    in the latter two cases). An internal routine recognizes the correct format 
256
    and converts them into decimal degrees.
257
 
258
    In addition, the tangent plane approximation restriction (i.e., dist < 90 deg) 
259
    has been removed and the complete formula is now implemented. 
260
    Pythagoras approximation is applied only for dist < 1 arcsec.
261
    """
262
    # ** ORIGINAL CODE **
263
    #
264
    #cRA = numpy.radians(RADeg1)
265
    #cDec = numpy.radians(decDeg1)
266

  
267
    #gRA = numpy.radians(RADeg2)
268
    #gDec = numpy.radians(decDeg2)
269

  
270
    #dRA = cRA-gRA
271
    #dDec = gDec-cDec
272
    #cosC = ((numpy.sin(gDec)*numpy.sin(cDec)) +
273
        #(numpy.cos(gDec)*numpy.cos(cDec) * numpy.cos(gRA-cRA)))
274
    #x = (numpy.cos(cDec)*numpy.sin(gRA-cRA))/cosC
275
    #y = (((numpy.cos(gDec)*numpy.sin(cDec)) - (numpy.sin(gDec) *
276
        #numpy.cos(cDec)*numpy.cos(gRA-cRA)))/cosC)
277
    #r = numpy.degrees(numpy.sqrt(x*x+y*y))
278

  
279
    RA1 = str(RA1).strip()
280
    dec1 = str(dec1).strip()
281
    RA2 = str(RA2).strip()
282
    dec2 = str(dec2).strip()
283
    
284
    inp_param = [str(RA1), dec1, RA2, dec2]
285
    newinp = []
286
    for x in inp_param: 
287
      newinp.append(string.replace(x, ":", " "))
288
      
289
    if str(newinp[0]).find(' ') >= 0:
290
      RADeg1 = hms2decimal(newinp[0], ' ')
291
      decDeg1 = dms2decimal(newinp[1], ' ')
292
    else:
293
      RADeg1 = float(newinp[0])
294
      decDeg1 = float(newinp[1])
295

  
296
    if str(newinp[2]).find(' ') >= 0:
297
      RADeg2 = hms2decimal(newinp[2], ' ')
298
      decDeg2 = dms2decimal(newinp[3], ' ')
299
    else:
300
      RADeg2 = float(newinp[2])
301
      decDeg2 = float(newinp[3])
302
	
303
    cRA = numpy.radians(RADeg1)
304
    cDec = numpy.radians(decDeg1)
305

  
306
    gRA = numpy.radians(RADeg2)
307
    gDec = numpy.radians(decDeg2)
308
      
309
    x=numpy.cos(cRA)*numpy.cos(cDec)*numpy.cos(gRA)*numpy.cos(gDec)
310
    y=numpy.sin(cRA)*numpy.cos(cDec)*numpy.sin(gRA)*numpy.cos(gDec)
311
    z=numpy.sin(cDec)*numpy.sin(gDec)
312

  
313
    rad=numpy.degrees(numpy.arccos(round(x+y+z,10)))
314

  
315
    dRA = cRA-gRA
316
    dDec = gDec-cDec
317
    #cosC = ((numpy.sin(gDec)*numpy.sin(cDec)) +
318
        #(numpy.cos(gDec)*numpy.cos(cDec) * numpy.cos(gRA-cRA)))
319
    #x = (numpy.cos(cDec)*numpy.sin(gRA-cRA))/cosC
320
    #y = (((numpy.cos(gDec)*numpy.sin(cDec)) - (numpy.sin(gDec) *
321
        #numpy.cos(cDec)*numpy.cos(gRA-cRA)))/cosC)
322
    #r = numpy.degrees(numpy.sqrt(x*x+y*y))
323
    
324
    # use Pythagoras approximation if rad < 1 arcsec
325
    if rad<0.000004848:
326
      rad=numpy.degrees(numpy.sqrt((numpy.cos(cDec)*dRA)**2+dDec**2))
327

  
328
    return rad
329
    
330
#-----------------------------------------------------------------------------
331
def calcAngSepDegPythagoras(RADeg1, decDeg1, RADeg2, decDeg2):
332
    # ** ORIGINAL CODE **
333
    #
334
    cRA = numpy.radians(RADeg1)
335
    cDec = numpy.radians(decDeg1)
336

  
337
    gRA = numpy.radians(RADeg2)
338
    gDec = numpy.radians(decDeg2)
339

  
340
    dRA = cRA-gRA
341
    dDec = gDec-cDec
342
    cosC = ((numpy.sin(gDec)*numpy.sin(cDec)) +
343
        (numpy.cos(gDec)*numpy.cos(cDec) * numpy.cos(gRA-cRA)))
344
    x = (numpy.cos(cDec)*numpy.sin(gRA-cRA))/cosC
345
    y = (((numpy.cos(gDec)*numpy.sin(cDec)) - (numpy.sin(gDec) *
346
        numpy.cos(cDec)*numpy.cos(gRA-cRA)))/cosC)
347
    r = numpy.degrees(numpy.sqrt(x*x+y*y))
348
  
349
    return r
350

  
351
#-----------------------------------------------------------------------------
352
def shiftRADec(ra1, dec1, deltaRA, deltaDec):
353
    """Computes new right ascension and declination shifted from the original
354
    by some delta RA and delta DEC. Input position is decimal degrees. Shifts
355
    (deltaRA, deltaDec) are arcseconds, and output is decimal degrees. Based on
356
    an IDL routine of the same name.
357

  
358
    @param ra1: float
359
    @type ra1: R.A. in decimal degrees
360
    @param dec1: float
361
    @type dec1: dec. in decimal degrees
362
    @param deltaRA: float
363
    @type deltaRA: shift in R.A. in arcseconds
364
    @param deltaDec: float
365
    @type deltaDec: shift in dec. in arcseconds
366
    @rtype: float [newRA, newDec]
367
    @return: shifted R.A. and dec.
368

  
369
    """
370

  
371
    d2r = math.pi/180.
372
    as2r = math.pi/648000.
373

  
374
    # Convert everything to radians
375
    rara1 = ra1*d2r
376
    dcrad1 = dec1*d2r
377
    shiftRArad = deltaRA*as2r
378
    shiftDCrad = deltaDec*as2r
379

  
380
    # Shift!
381
    deldec2 = 0.0
382
    sindis = math.sin(shiftRArad / 2.0)
383
    sindelRA = sindis / math.cos(dcrad1)
384
    delra = 2.0*math.asin(sindelRA) / d2r
385

  
386
    # Make changes
387
    ra2 = ra1+delra
388
    dec2 = dec1 +deltaDec / 3600.0
389

  
390
    return ra2, dec2
391

  
392
#-----------------------------------------------------------------------------
393
def convertCoords(inputSystem, outputSystem, coordX, coordY, epoch):
394
    """Converts specified coordinates (given in decimal degrees) between J2000,
395
    B1950, and Galactic.
396

  
397
    @type inputSystem: string
398
    @param inputSystem: system of the input coordinates (either "J2000",
399
        "B1950" or "GALACTIC")
400
    @type outputSystem: string
401
    @param outputSystem: system of the returned coordinates (either "J2000",
402
        "B1950" or "GALACTIC")
403
    @type coordX: float
404
    @param coordX: longitude coordinate in decimal degrees, e.g. R. A.
405
    @type coordY: float
406
    @param coordY: latitude coordinate in decimal degrees, e.g. dec.
407
    @type epoch: float
408
    @param epoch: epoch of the input coordinates
409
    @rtype: list
410
    @return: coordinates in decimal degrees in requested output system
411

  
412
    """
413

  
414
    if inputSystem=="J2000" or inputSystem=="B1950" or inputSystem=="GALACTIC":
415
        if outputSystem=="J2000" or outputSystem=="B1950" or \
416
            outputSystem=="GALACTIC":
417

  
418
            outCoords=wcscon.wcscon(wcscon.wcscsys(inputSystem),
419
                wcscon.wcscsys(outputSystem), 0, 0, coordX, coordY, epoch)
420

  
421
            return outCoords
422

  
423
    raise Exception("inputSystem and outputSystem must be 'J2000', 'B1950'"
424
                    "or 'GALACTIC'")
425

  
426
#-----------------------------------------------------------------------------
427
def calcRADecSearchBox(RADeg, decDeg, radiusSkyDeg):
428
    """Calculates minimum and maximum RA, dec coords needed to define a box
429
    enclosing a circle of radius radiusSkyDeg around the given RADeg, decDeg
430
    coordinates. Useful for freeform queries of e.g. SDSS, UKIDSS etc.. Uses
431
    L{calcAngSepDeg}, so has the same limitations.
432

  
433
    @type RADeg: float
434
    @param RADeg: RA coordinate of centre of search region
435
    @type decDeg: float
436
    @param decDeg: dec coordinate of centre of search region
437
    @type radiusSkyDeg: float
438
    @param radiusSkyDeg: radius in degrees on the sky used to define search
439
        region
440
    @rtype: list
441
    @return: [RAMin, RAMax, decMin, decMax] - coordinates in decimal degrees
442
        defining search box
443

  
444
    """
445

  
446
    tolerance = 1e-5  # in degrees on sky
447
    targetHalfSizeSkyDeg = radiusSkyDeg
448
    funcCalls = ["calcAngSepDeg(RADeg, decDeg, guess, decDeg)",
449
               "calcAngSepDeg(RADeg, decDeg, guess, decDeg)",
450
               "calcAngSepDeg(RADeg, decDeg, RADeg, guess)",
451
               "calcAngSepDeg(RADeg, decDeg, RADeg, guess)"]
452
    coords = [RADeg, RADeg, decDeg, decDeg]
453
    signs = [1.0, -1.0, 1.0, -1.0]
454
    results = []
455
    for f, c, sign in zip(funcCalls, coords, signs):
456
        # Initial guess range
457
        maxGuess = sign*targetHalfSizeSkyDeg*10.0
458
        minGuess = sign*targetHalfSizeSkyDeg/10.0
459
        guessStep = (maxGuess-minGuess)/10.0
460
        guesses = numpy.arange(minGuess+c, maxGuess+c, guessStep)
461
        for i in range(50):
462
            minSizeDiff = 1e6
463
            bestGuess = None
464
            for guess in guesses:
465
                sizeDiff = abs(eval(f)-targetHalfSizeSkyDeg)
466
                if sizeDiff < minSizeDiff:
467
                    minSizeDiff = sizeDiff
468
                    bestGuess = guess
469
            if minSizeDiff < tolerance:
470
                break
471
            else:
472
                guessRange = abs((maxGuess-minGuess))
473
                maxGuess = bestGuess+guessRange/4.0
474
                minGuess = bestGuess-guessRange/4.0
475
                guessStep = (maxGuess-minGuess)/10.0
476
                guesses = numpy.arange(minGuess, maxGuess, guessStep)
477
        results.append(bestGuess)
478

  
479
    RAMax = results[0]
480
    RAMin = results[1]
481
    decMax = results[2]
482
    decMin = results[3]
483

  
484
    return [RAMin, RAMax, decMin, decMax]
common/table_creation/edit_FITS_table.py
1
#!/usr/bin/python
2

  
3
# ******************************************************************************
4
#    Copyright 2015 IAS - IDOC
5
#
6
#    This program is free software: you can redistribute it and/or modify
7
#    it under the terms of the GNU General Public License as published by
8
#    the Free Software Foundation, either version 3 of the License, or
9
#    (at your option) any later version.
10
#
11
#    This program is distributed in the hope that it will be useful,
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#    GNU General Public License for more details.
15
#
16
#    You should have received a copy of the GNU General Public License
17
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19
# ******************************************************************************
20
'''
21
This script updates the content of a fits table, adding new columns
22
and/or rows (i.e. objects) to it, by considering as input a user-defined ASCII table.                       
23

  
24
The new columns (rows) defined in the ascii file are appended at the end
25
(bottom) of the fits table.                                             
26
                                                                           
27
IMPORTANT: The 1st line of the ASCII table must contain the names   
28
of the columns, and must be UNCOMMENTED!                            
29

  
30
NOTE: Ra and DEC must be in **decimal degrees**, both in FITS and                                          
31
ASCII tables.
32

  
33
The syntax is:
34

  
35
$ python edit_FITS.py <table>.fits <ascii_file>
36

  
37
@author: Alessandro NASTASI for IAS - IDOC 
38
@date: 21/05/2015
39
'''
40
__author__ = "Alessandro Nastasi"
41
__credits__ = ["Alessandro Nastasi"]
42
__license__ = "GPL"
43
__version__ = "1.0"
44
__date__ = "21/05/2015"
45

  
46
import numpy as np
47
import os, sys, re, time
48
import string
49
import asciidata
50
import pyfits
51
from datetime import date
52

  
53
#Use the provided astCoords.py file rather than the default module of astLib, 
54
#since the calcAngSepDeg() of the latter works only for separation <90 deg 
55
#(tangent plane projection approximation)
56
import astCoords
57

  
58
class bcolors:
59
    HEADER = '\033[95m'
60
    OKBLUE = '\033[94m'
61
    OKGREEN = '\033[92m'
62
    WARNING = '\033[93m'
63
    FAIL = '\033[91m'
64
    ENDC = '\033[0m'
65
    
66
#Dictionary containing the FORMAT and UNITS of all (or most of) the fields
67
_FIELDS_DICTIONARY = { 
68
  'INDEX': { 'format': 'I', 'unit': 'None' },
69
  'COORD_SOURCE': { 'format': '5A', 'unit': 'None' },
70
  'x':{ 'format': 'E', 'unit': 'None' },
71
  'y':{ 'format': 'E', 'unit': 'None' },
72
  'z':{ 'format': 'E', 'unit': 'None' },
73
  
74
  # ****** ACT ******
75
  'ACT_INDEX': { 'format': 'I', 'unit': 'None' },
76
  'INDEX_ACT': { 'format': 'I', 'unit': 'None' },
77
  'CATALOG': { 'format': '7A', 'unit': 'None' },
78
  #'NAME': { 'format': '18A', 'unit': 'None' },
79
  #'GLON': { 'format': 'E', 'unit': 'degrees' },
80
  #'GLAT': { 'format': 'E', 'unit': 'degrees' },
81
  #'RA': { 'format': 'E', 'unit': 'degrees' },
82
  #'DEC': { 'format': 'E', 'unit': 'degrees' },
83
  'SNR': { 'format': 'E', 'unit': 'None' },
84
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
85
  'ERR_REDSHIFT': { 'format': 'E', 'unit': 'None' },
86
  #'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
87
  'M500': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
88
  'ERR_M500': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
89
  'YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
90
  'ERR_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
91
  'THETA': { 'format': 'E', 'unit': 'arcmin' },
92
  #'PAPER': { 'format': '56A', 'unit': 'None' },      # Use PAPER in SPT as bigger format '59A'
93

  
94
  'ACT_CATALOG': { 'format': '7A', 'unit': 'None' },
95
  'ACT_NAME': { 'format': '18A', 'unit': 'None' },
96
  'ACT_GLON': { 'format': 'E', 'unit': 'degrees' },
97
  'ACT_GLAT': { 'format': 'E', 'unit': 'degrees' },
98
  'ACT_RA': { 'format': 'E', 'unit': 'degrees' },
99
  'ACT_DEC': { 'format': 'E', 'unit': 'degrees' },
100
  'ACT_SNR': { 'format': 'E', 'unit': 'None' },
101
  'ACT_REDSHIFT': { 'format': 'E', 'unit': 'None' },
102
  'ACT_ERR_REDSHIFT': { 'format': 'E', 'unit': 'None' },
103
  'ACT_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
104
  'ACT_REDSHIFT_REF': { 'format': '19A', 'unit': 'None' },
105
  'ACT_M500': { 'format': 'E', 'unit': '10^14 h^-1 solar mass' },
106
  'ACT_ERR_M500': { 'format': 'E', 'unit': '10^14 h^-1 solar mass' },
107
  'ACT_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
108
  'ACT_ERR_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
109
  'ACT_THETA': { 'format': 'E', 'unit': 'arcmin' },
110
  'ACT_PAPER': { 'format': '56A', 'unit': 'None' },
111

  
112
  # ****** AMI ******
113
  'INDEX_AMI': { 'format': 'I', 'unit': 'None' },
114
  'AMI_INDEX': { 'format': 'I', 'unit': 'None' },
115
  #'NAME': { 'format': '18A', 'unit': 'None' },
116
  #'RA': { 'format': 'E', 'unit': 'Degrees' },
117
  #'DEC': { 'format': 'E', 'unit': 'Degrees' },
118
  #'GLON': { 'format': 'E', 'unit': 'Degrees' },
119
  #'GLAT': { 'format': 'E', 'unit': 'Degrees' },
120
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
121
  #'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
122
  #'REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
123
  #'ALT_NAME': { 'format': '60A', 'unit': 'None' },
124
  #'COORD_SOURCE': { 'format': '5A', 'unit': 'None' },
125

  
126
  'AMI_NAME': { 'format': '18A', 'unit': 'None' },
127
  'AMI_RA': { 'format': 'E', 'unit': 'Degrees' },
128
  'AMI_DEC': { 'format': 'E', 'unit': 'Degrees' },
129
  'AMI_GLON': { 'format': 'E', 'unit': 'Degrees' },
130
  'AMI_GLAT': { 'format': 'E', 'unit': 'Degrees' },
131
  'AMI_REDSHIFT': { 'format': 'E', 'unit': 'None' },
132
  'AMI_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
133
  'AMI_REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
134
  'AMI_ALT_NAME': { 'format': '60A', 'unit': 'None' },
135
  
136
  # ****** CARMA ******
137
  'INDEX_CARMA': { 'format': 'I', 'unit': 'None' },
138
  'CARMA_INDEX': { 'format': 'I', 'unit': 'None' },
139
  #'NAME': { 'format': '18A', 'unit': 'None' },
140
  #'RA': { 'format': 'E', 'unit': 'Degrees' },
141
  #'DEC': { 'format': 'E', 'unit': 'Degrees' },
142
  #'GLON': { 'format': 'E', 'unit': 'Degrees' },
143
  #'GLAT': { 'format': 'E', 'unit': 'Degrees' },
144
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
145
  #'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
146
  #'REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
147
  #'COORD_SOURCE': { 'format': '5A', 'unit': 'None' },  
148
  
149
  'CARMA_NAME': { 'format': '18A', 'unit': 'None' },
150
  'CARMA_RA': { 'format': 'E', 'unit': 'Degrees' },
151
  'CARMA_DEC': { 'format': 'E', 'unit': 'Degrees' },
152
  'CARMA_GLON': { 'format': 'E', 'unit': 'Degrees' },
153
  'CARMA_GLAT': { 'format': 'E', 'unit': 'Degrees' },
154
  'CARMA_REDSHIFT': { 'format': 'E', 'unit': 'None' },
155
  'CARMA_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
156
  'CARMA_REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
157
  'CARMA_M500': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
158
  'CARMA_ERR_M500': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
159

  
160
  #****** PSZ1 ******
161
  'PSZ1_INDEX': { 'format': 'I', 'unit': 'None' },
162
  'INDEX_PSZ1': { 'format': 'I', 'unit': 'None' },
163
  'NAME': { 'format': '18A', 'unit': 'None' },
164
  'GLON': { 'format': 'D', 'unit': 'degrees' },
165
  'GLAT': { 'format': 'D', 'unit': 'degrees' },
166
  'RA': { 'format': 'D', 'unit': 'degrees' },
167
  'DEC': { 'format': 'D', 'unit': 'degrees' },
168
  'RA_MCXC': { 'format': 'E', 'unit': 'degrees' },
169
  'DEC_MCXC': { 'format': 'E', 'unit': 'degrees' },
170
  'REDSHIFT': { 'format': 'E', 'unit': 'None' },
171
  'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
172
  'REDSHIFT_SOURCE': { 'format': 'I', 'unit': 'None' },
173
  'REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
174
  'ALT_NAME': { 'format': '66A', 'unit': 'None' },
175
  'YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
176
  'ERRP_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
177
  'ERRM_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
178
  'M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
179
  'ERRP_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
180
  'ERRM_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
181
  'S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
182
  'ERR_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
183
  'Y_PSX_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
184
  'SN_PSX': { 'format': 'E', 'unit': 'None' },
185
  'PIPELINE': { 'format': 'I', 'unit': 'None' },
186
  'PIPE_DET': { 'format': 'I', 'unit': 'None' },
187
  'PCCS': { 'format': 'L', 'unit': 'None' },
188
  'VALIDATION': { 'format': 'I', 'unit': 'None' },
189
  'ID_EXT': { 'format': '25A', 'unit': 'None' },
190
  'POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
191
  #'SNR': { 'format': 'E', 'unit': 'None' },
192
  'COSMO': { 'format': 'L', 'unit': 'None' },
193
  'COMMENT': { 'format': 'L', 'unit': 'None' },
194
  'QN': { 'format': 'E', 'unit': 'None' }, 
195

  
196
  'PSZ1_NAME': { 'format': '18A', 'unit': 'None' },
197
  'PSZ1_GLON': { 'format': 'D', 'unit': 'degrees' },
198
  'PSZ1_GLAT': { 'format': 'D', 'unit': 'degrees' },
199
  'PSZ1_RA': { 'format': 'D', 'unit': 'degrees' },
200
  'PSZ1_DEC': { 'format': 'D', 'unit': 'degrees' },
201
  'PSZ1_RA_MCXC': { 'format': 'E', 'unit': 'degrees' },
202
  'PSZ1_DEC_MCXC': { 'format': 'E', 'unit': 'degrees' },
203
  'PSZ1_REDSHIFT': { 'format': 'E', 'unit': 'None' },
204
  'PSZ1_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
205
  'PSZ1_REDSHIFT_SOURCE': { 'format': 'I', 'unit': 'None' },
206
  'PSZ1_REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
207
  'PSZ1_ALT_NAME': { 'format': '66A', 'unit': 'None' },
208
  'PSZ1_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
209
  'PSZ1_ERRP_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
210
  'PSZ1_ERRM_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
211
  'PSZ1_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
212
  'PSZ1_ERRP_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
213
  'PSZ1_ERRM_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
214
  'PSZ1_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
215
  'PSZ1_ERR_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
216
  'PSZ1_Y_PSX_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
217
  'PSZ1_SN_PSX': { 'format': 'E', 'unit': 'None' },
218
  'PSZ1_PIPELINE': { 'format': 'I', 'unit': 'None' },
219
  'PSZ1_PIPE_DET': { 'format': 'I', 'unit': 'None' },
220
  'PSZ1_PCCS': { 'format': 'L', 'unit': 'None' },
221
  'PSZ1_VALIDATION': { 'format': 'I', 'unit': 'None' },
222
  'PSZ1_ID_EXT': { 'format': '25A', 'unit': 'None' },
223
  'PSZ1_POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
224
  'PSZ1_SNR': { 'format': 'E', 'unit': 'None' },
225
  'PSZ1_COSMO': { 'format': 'L', 'unit': 'None' },
226
  'PSZ1_COMMENT': { 'format': 'L', 'unit': 'None' },
227
  'PSZ1_QN': { 'format': 'E', 'unit': 'None' }, 
228

  
229
  # ****** PSZ2 ******
230
  'PSZ2_INDEX': { 'format': 'I', 'unit': 'None' },
231
  'INDEX_PSZ2': { 'format': 'I', 'unit': 'None' },
232
  #'NAME': { 'format': '18A', 'unit': 'None' },
233
  #'GLON': { 'format': 'D', 'unit': 'degrees' },
234
  #'GLAT': { 'format': 'D', 'unit': 'degrees' },
235
  #'RA': { 'format': 'D', 'unit': 'degrees' },
236
  #'DEC': { 'format': 'D', 'unit': 'degrees' },
237
  #'POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
238
  #'SNR': { 'format': 'E', 'unit': 'None' },
239
  #'PIPELINE': { 'format': 'I', 'unit': 'None' },
240
  #'PIPE_DET': { 'format': 'I', 'unit': 'None' },
241
  'PCCS2': { 'format': 'L', 'unit': 'None' },
242
  'PSZ': { 'format': 'I', 'unit': 'None' },
243
  'IR_FLAG': { 'format': 'I', 'unit': 'None' },
244
  'Q_NEURAL': { 'format': 'E', 'unit': 'None' },
245
  'Y5R500': { 'format': 'E', 'unit': '10^-3 arcmin^2' },
246
  'Y5R500_ERR': { 'format': 'E', 'unit': '10^-3 arcmin^2' },
247
  'PSZ2_VALIDATION': { 'format': 'I', 'unit': 'None' },
248
  'REDSHIFT_ID': { 'format': '25A', 'unit': 'None' },
249
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
250
  'MSZ': { 'format': 'E', 'unit': '10^14 Msol' },
251
  'MSZ_ERR_UP': { 'format': 'E', 'unit': '10^14 Msol' },
252
  'MSZ_ERR_LOW': { 'format': 'E', 'unit': '10^14 Msol' },
253
  'MCXC': { 'format': '25A', 'unit': 'None' },
254
  'REDMAPPER': { 'format': '25A', 'unit': 'None' },
255
  'ACT': { 'format': '25A', 'unit': 'None' },
256
  'SPT': { 'format': '25A', 'unit': 'None' },
257
  'WISE_SIGNF': { 'format': 'E', 'unit': 'None' },
258
  'WISE_FLAG': { 'format': 'I', 'unit': 'None' },
259
  'AMI_EVIDENCE': { 'format': 'E', 'unit': 'None' },
260
  #'COSMO': { 'format': 'L', 'unit': 'None' },
261
  'PSZ2_COMMENT': { 'format': '128A', 'unit': 'None' },
262

  
263
  'PSZ2_NAME': { 'format': '18A', 'unit': 'None' },
264
  'PSZ2_GLON': { 'format': 'D', 'unit': 'degrees' },
265
  'PSZ2_GLAT': { 'format': 'D', 'unit': 'degrees' },
266
  'PSZ2_RA': { 'format': 'D', 'unit': 'degrees' },
267
  'PSZ2_DEC': { 'format': 'D', 'unit': 'degrees' },
268
  'PSZ2_POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
269
  'PSZ2_SNR': { 'format': 'E', 'unit': 'None' },
270
  'PSZ2_PIPELINE': { 'format': 'I', 'unit': 'None' },
271
  'PSZ2_PIPE_DET': { 'format': 'I', 'unit': 'None' },
272
  'PSZ2_PCCS2': { 'format': 'L', 'unit': 'None' },
273
  'PSZ2_PSZ': { 'format': 'I', 'unit': 'None' },
274
  'PSZ2_IR_FLAG': { 'format': 'I', 'unit': 'None' },
275
  'PSZ2_Q_NEURAL': { 'format': 'E', 'unit': 'None' },
276
  'PSZ2_Y5R500': { 'format': 'E', 'unit': '10^-3 arcmin^2' },
277
  'PSZ2_Y5R500_ERR': { 'format': 'E', 'unit': '10^-3 arcmin^2' },
278
  #'PSZ2_VALIDATION': { 'format': 'I', 'unit': 'None' },
279
  'PSZ2_REDSHIFT_ID': { 'format': '25A', 'unit': 'None' },
280
  'PSZ2_REDSHIFT': { 'format': 'E', 'unit': 'None' },
281
  'PSZ2_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
282
  'PSZ2_MSZ': { 'format': 'E', 'unit': '10^14 Msol' },
283
  'PSZ2_MSZ_ERR_UP': { 'format': 'E', 'unit': '10^14 Msol' },
284
  'PSZ2_MSZ_ERR_LOW': { 'format': 'E', 'unit': '10^14 Msol' },
285
  'PSZ2_MCXC': { 'format': '25A', 'unit': 'None' },
286
  'PSZ2_REDMAPPER': { 'format': '25A', 'unit': 'None' },
287
  'PSZ2_ACT': { 'format': '25A', 'unit': 'None' },
288
  'PSZ2_SPT': { 'format': '25A', 'unit': 'None' },
289
  'PSZ2_WISE_SIGNF': { 'format': 'E', 'unit': 'None' },
290
  'PSZ2_WISE_FLAG': { 'format': 'I', 'unit': 'None' },
291
  'PSZ2_AMI_EVIDENCE': { 'format': 'E', 'unit': 'None' },
292
  'PSZ2_COSMO': { 'format': 'L', 'unit': 'None' },
293
  #'PSZ2_COMMENT': { 'format': '128A', 'unit': 'None' },
294

  
295
  # ****** PLCK ******
296
  'PLCK_INDEX': { 'format': 'I', 'unit': 'None' },
297
  'INDEX_PLCK': { 'format': 'I', 'unit': 'None' },
298

  
299
  #'NAME': { 'format': '18A', 'unit': 'None' },
300
  #'GLON': { 'format': 'D', 'unit': 'degrees' },
301
  #'GLAT': { 'format': 'D', 'unit': 'degrees' },
302
  #'RA': { 'format': 'D', 'unit': 'degrees' },
303
  #'DEC': { 'format': 'D', 'unit': 'degrees' },
304
  #'RA_MCXC': { 'format': 'E', 'unit': 'degrees' },
305
  #'DEC_MCXC': { 'format': 'E', 'unit': 'degrees' },
306
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
307
  #'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
308
  #'REDSHIFT_SOURCE': { 'format': 'I', 'unit': 'None' },
309
  #'REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
310
  #'ALT_NAME': { 'format': '66A', 'unit': 'None' },
311
  #'YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
312
  #'ERRP_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
313
  #'ERRM_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
314
  #'M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
315
  #'ERRP_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
316
  #'ERRM_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
317
  #'S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
318
  #'ERR_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
319
  #'Y_PSX_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
320
  #'SN_PSX': { 'format': 'E', 'unit': 'None' },
321
  #'PIPELINE': { 'format': 'I', 'unit': 'None' },
322
  #'PIPE_DET': { 'format': 'I', 'unit': 'None' },
323
  #'PCCS': { 'format': 'L', 'unit': 'None' },
324
  #'VALIDATION': { 'format': 'I', 'unit': 'None' },
325
  #'ID_EXT': { 'format': '25A', 'unit': 'None' },
326
  #'POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
327
  #'SNR': { 'format': 'E', 'unit': 'None' },
328
  #'COSMO': { 'format': 'L', 'unit': 'None' },
329
  #'COMMENT': { 'format': 'L', 'unit': 'None' },
330
  #'QN': { 'format': 'E', 'unit': 'None' }, 
331

  
332
  'PLCK_NAME': { 'format': '18A', 'unit': 'None' },
333
  'PLCK_GLON': { 'format': 'D', 'unit': 'degrees' },
334
  'PLCK_GLAT': { 'format': 'D', 'unit': 'degrees' },
335
  'PLCK_RA': { 'format': 'D', 'unit': 'degrees' },
336
  'PLCK_DEC': { 'format': 'D', 'unit': 'degrees' },
337
  'PLCK_RA_MCXC': { 'format': 'E', 'unit': 'degrees' },
338
  'PLCK_DEC_MCXC': { 'format': 'E', 'unit': 'degrees' },
339
  'PLCK_REDSHIFT': { 'format': 'E', 'unit': 'None' },
340
  'PLCK_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
341
  'PLCK_REDSHIFT_SOURCE': { 'format': 'I', 'unit': 'None' },
342
  'PLCK_REDSHIFT_REF': { 'format': '36A', 'unit': 'None' },
343
  'PLCK_ALT_NAME': { 'format': '66A', 'unit': 'None' },
344
  'PLCK_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
345
  'PLCK_ERRP_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
346
  'PLCK_ERRM_YZ_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
347
  'PLCK_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
348
  'PLCK_ERRP_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
349
  'PLCK_ERRM_M_YZ_500': { 'format': 'E', 'unit': '10^14 solar mass' },
350
  'PLCK_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
351
  'PLCK_ERR_S_X': { 'format': 'E', 'unit': 'erg/s/cm2' },
352
  'PLCK_Y_PSX_500': { 'format': 'E', 'unit': '10^-4 arcmin squared' },
353
  'PLCK_SN_PSX': { 'format': 'E', 'unit': 'None' },
354
  'PLCK_PIPELINE': { 'format': 'I', 'unit': 'None' },
355
  'PLCK_PIPE_DET': { 'format': 'I', 'unit': 'None' },
356
  'PLCK_PCCS': { 'format': 'L', 'unit': 'None' },
357
  'PLCK_VALIDATION': { 'format': 'I', 'unit': 'None' },
358
  'PLCK_ID_EXT': { 'format': '25A', 'unit': 'None' },
359
  'PLCK_POS_ERR': { 'format': 'E', 'unit': 'arcmin' },
360
  'PLCK_SNR': { 'format': 'E', 'unit': 'None' },
361
  'PLCK_COSMO': { 'format': 'L', 'unit': 'None' },
362
  'PLCK_COMMENT': { 'format': 'L', 'unit': 'None' },
363
  'PLCK_QN': { 'format': 'E', 'unit': 'None' }, 
364

  
365
  # ****** SPT ******
366
  'SPT_INDEX': { 'format': 'I', 'unit': 'None' },
367
  'INDEX_SPT': { 'format': 'I', 'unit': 'None' },
368
  #'CATALOG': { 'format': '7A', 'unit': 'None' },
369
  #'NAME': { 'format': '16A', 'unit': 'None' },
370
  #'GLON': { 'format': 'E', 'unit': 'degrees' },
371
  #'GLAT': { 'format': 'E', 'unit': 'degrees' },
372
  #'RA': { 'format': 'E', 'unit': 'degrees' },
373
  #'DEC': { 'format': 'E', 'unit': 'degrees' },
374
  #'SNR': { 'format': 'E', 'unit': 'None' },
375
  #'REDSHIFT': { 'format': 'E', 'unit': 'None' },
376
  #'ERR_REDSHIFT': { 'format': 'E', 'unit': 'None' },
377
  #'REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
378
  'REDSHIFT_LIMIT': { 'format': 'E', 'unit': 'None' },
379
  
380
  'M500_fidCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
381
  'ERR_M500_fidCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
382
  'M500_PlanckCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
383
  'ERR_M500_PlanckCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
384
  'YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
385
  'ERR_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
386
    
387
  'LX': { 'format': 'E', 'unit': '10^44 erg/s' },
388
  'YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
389
  'ERR_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
390
  #'THETA': { 'format': 'E', 'unit': 'arcmin' },
391
  'PAPER': { 'format': '59A', 'unit': 'None' },
392
  'XRAY': { 'format': 'L', 'unit': 'None' },
393
  'STRONG_LENS': { 'format': 'L', 'unit': 'None' },
394
  
395
  'SPT_CATALOG': { 'format': '7A', 'unit': 'None' },
396
  'SPT_NAME': { 'format': '16A', 'unit': 'None' },
397
  'SPT_GLON': { 'format': 'E', 'unit': 'degrees' },
398
  'SPT_GLAT': { 'format': 'E', 'unit': 'degrees' },
399
  'SPT_RA': { 'format': 'E', 'unit': 'degrees' },
400
  'SPT_DEC': { 'format': 'E', 'unit': 'degrees' },
401
  'SPT_SNR': { 'format': 'E', 'unit': 'None' },
402
  'SPT_REDSHIFT': { 'format': 'E', 'unit': 'None' },
403
  'SPT_ERR_REDSHIFT': { 'format': 'E', 'unit': 'None' },
404
  'SPT_REDSHIFT_TYPE': { 'format': '5A', 'unit': 'None' },
405
  'SPT_REDSHIFT_REF': { 'format': '19A', 'unit': 'None' },
406

  
407
  'SPT_REDSHIFT_LIMIT': { 'format': 'E', 'unit': 'None' },
408
  'SPT_XRAY': { 'format': 'L', 'unit': 'None' },
409
  'SPT_STRONG_LENS': { 'format': 'L', 'unit': 'None' },
410

  
411
  'SPT_M500_fidCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
412
  'SPT_ERR_M500_fidCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
413
  'SPT_M500_PlanckCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
414
  'SPT_ERR_M500_PlanckCosmo': { 'format': 'E', 'unit': '10^14 h70^-1 solar mass' },
415
  
416
  'SPT_LX': { 'format': 'E', 'unit': '10^44 erg/s' },
417
  'SPT_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
418
  'SPT_ERR_YSZ': { 'format': 'E', 'unit': '10^-6 arcmin squared' },
419
  'SPT_THETA': { 'format': 'E', 'unit': 'arcmin' },
420
  'SPT_PAPER': { 'format': '59A', 'unit': 'None' }
421
  
422
  }
423
  
424
#Name of fields (in FITS/ASCII) sometimes called individually in the script
425

  
426
#For Mass and Err_Mass, more than one label could be defined just as elements of the arrays
427
name_mass_key = ['M500']
428
name_errMass_key = ['ERR_M500']
429

  
430
name_ra_key = 'RA'
431
name_dec_key = 'DEC'
432
name_coordinates_keys =  ['RA_MCXC', 'DEC_MCXC', name_ra_key, name_dec_key]
433

  
434
name_Name_key = 'NAME'  
435
name_index_key = 'INDEX'
436
name_catalog_key = 'CATALOG'
437
name_redshift_key = 'REDSHIFT'
438
name_zLimit_key = 'REDSHIFT_LIMIT'
439
name_zErr_key = 'ERR_REDSHIFT'
440
name_zType_key = 'REDSHIFT_TYPE'
441
name_zRef_key = 'REDSHIFT_REF'
442
name_altName_key = 'ALT_NAME'
443
name_paper_key = 'PAPER'
444

  
445
#Undef values for some kind of fields
446
_UNDEF_VALUES_ = {
447
  'FLOAT' : {np.nan},
448
  'INT' : {-1},
449
  'STRING' : {'NULL'},
450
  name_zType_key : {'undef'},
451
  'PIPELINE' : {0},
452
  'PIPE_DET' : {0}
453
  }
454

  
455
def remove_duplicated_names(string):
456
  '''
457
  This function removes duplicated names of a string, assuming they are separated by ';'
458
  In addition, it takes out 'NULL', 'NaN', 'False' from the final, composite string.
459
  It is used for the creation of ALT_NAME field.
460
  '''
461
  string = string.replace('; ',';')
462
  tmp = [item for item in string.split(';') if item.upper() not in ["-", "NULL", "NAN", "NONE", "FALSE"] and len(item)>0 ]
463
  # *** This lines of code help preserving the order ot the names ***
464
  tmp_uniq = []
465
  set_tmp = set()
466
  for item in tmp:
467
    if item not in set_tmp:
468
      tmp_uniq.append(item)
469
      set_tmp.add(item)
470
  # ******************************************************************
471
  
472
  if len(tmp)==0: new_string = '-'
473
  else: new_string =  "; ".join(tmp_uniq)
474
  return new_string
475
 
476
def set_undef_values(fits_data):
477
  '''
478
  Set the proper 'undef' values according to the format/name of the field
479
  '''
480
  print "\n\t>> Checking/setting undefined values for the different fields ..."
481
  for i, name in enumerate(fits_data.names):
482
    sys.stdout.write('\t%i/%i > %s (format %s) : Done                                        \r' % (i+1, len(fits_data.names), name, fits_data.formats[i]))
483
    sys.stdout.flush()
484
    for j in range(fits_data.size):
485
      if name == name_index_key:
486
	if fits_data[name_Name_key][j] <= 0: fits_data[j][i] = -1
487
      elif name == name_redshift_key and fits_data[name][j] == -1.0:
488
	fits_data[j][i] = np.nan
489
      elif name.find(name_zType_key) >= 0 and str(fits_data[name][j]) == 'Null':
490
	fits_data[j][i] = "undef"
491
      elif fits_data.formats[i] in 'EDJ':
492
	if str(fits_data[j][i]) in ['-1.6375e+30','-1.63750e+30', '-1.6375E+30', '-1.63750E+30', 'None', 'NULL']:
493
	  fits_data[j][i] = np.nan
494
      elif fits_data.formats[i].find('A') >= 0:
495
	fits_data[j][i] = remove_duplicated_names(fits_data[j][i])
496
	if str(fits_data[j][i]).upper() in ["", "0.0", "NULL", "NAN", "NONE", "FALSE"]  or str(fits_data[j][i]) == 'False':
497
	  fits_data[j][i] = "-"
498
      elif name in ['PIPELINE','PIPE_DET']:
499
	if fits_data[j][i] <= 0: fits_data[j][i] = 0
500
  print '\n'
501
  return fits_data
502

  
503
def recreate_reformatted_column(hdulist, field_name, new_format, new_vector):
504
  '''
505
  Update the length (format) of a 'STRING' (format = 'xA') FIELD. 
506
  The only way, though, is to re-create the column with the new format.
507
  It is used during the creation of NAME, ALT_NAME or REDSHIFT_REF.
508
  '''
509
  name_vec = []
510
  format_vec = []
511
  unit_vec = []
512
  
513
  fits_keywds = hdulist.data.names
514
  coldefs = pyfits.ColDefs(hdulist.columns)
515
  
516
  #Store attributes of the kewyords after FIELD
517
  for j in range(fits_keywds.index(field_name)+1, len(fits_keywds)):
518
    name_vec.append(coldefs.names[j])
519
    format_vec.append(coldefs.formats[j])
520
    unit_vec.append(coldefs.units[j])
521
  
522
  #Delete the kewyords after FIELD
523
  tmp = 0
524
  for j in range(fits_keywds.index(field_name)+1, len(fits_keywds)):
525
    coldefs.del_col(name_vec[tmp])
526
    tmp+=1
527
    
528
  #Delete FIELD
529
  coldefs.del_col(field_name)
530

  
531
  #Re-create FIELD with the new format
532
  col_tmp = pyfits.Column(name = field_name, format = new_format, unit = 'None', array = new_vector)
533
  coldefs.add_col(col_tmp)
534
  hdulist.columns = coldefs
535
  
536
  #Re-create all the kewyords after FIELD, with their attributes
537
  tmp = 0
538
  data_vec_tmp = []
539
  for j in range(fits_keywds.index(field_name)+1, len(fits_keywds)):
540
    data_vec_tmp = hdulist.data[name_vec[tmp]]
541
    col_tmp = pyfits.Column(name = name_vec[tmp], format = format_vec[tmp], unit = unit_vec[tmp], array = data_vec_tmp)
542
    coldefs.add_col(col_tmp)
543
    tmp +=1
544
    data_vec_tmp = []
545
    
546
  hdulist = pyfits.new_table(coldefs)
547
  return hdulist
548

  
549
'''
550
  *** >> START << ***
551
'''
552

  
553
if (len(sys.argv) > 1):
554
    fits_file = sys.argv[1] 
555
    ascii_file = sys.argv[2] 
556
else:
557
    print bcolors.WARNING +  "\n\tSintax:\t$ python edit_FITS.py <fits_file> <ascii_file>\n" + bcolors.ENDC
558
    os._exit(0)
559

  
560
#Open the output file
561
file_report_name = 'summary_updates.tab'
562
file_report = open(file_report_name, 'w')
563

  
564
question = bcolors.OKBLUE+ "[Q]" + bcolors.ENDC
565
info = bcolors.WARNING+ "[I]" + bcolors.ENDC
566
error = bcolors.FAIL+ "[ERR]" + bcolors.ENDC
567

  
568
#User can define the columns delimiter in the ASCII table.
569
delim=raw_input("\n%s Please enter the column delimiter of the ASCII table (default is ','):\t" % question)
570
if not delim:
571
# Read the ascii table, with the structure: ascii_table[COLUMNS][ROWS]
572
  ascii_table=asciidata.open(ascii_file, 'r', delimiter=',')
573
else:
574
  ascii_table=asciidata.open(ascii_file, 'r', delimiter=delim)
575
  
576
Ncol_ascii = ascii_table.ncols
577
Nrows_ascii = (ascii_table.nrows) - 1 #1st excluded because of the header
578

  
579
print "\n\t\t **** ASCII table details ****"
580
print "\t\t Number of columns: %s" % (Ncol_ascii)
581
print "\t\t Number of rows: %s" % (Nrows_ascii)
582
print "\t\t **** **** **** **** **** ****"
583

  
584
ascii_keywds=[]
585
keys_form_unit = {}
586

  
587
for i in range(ascii_table.ncols):
588
  tmpKey = str(ascii_table[i][0]).strip()
589
  ascii_keywds.append(tmpKey)
590
  if tmpKey in _FIELDS_DICTIONARY:
591
    keys_form_unit[tmpKey] = {}
592
    keys_form_unit[tmpKey]['TFORM'] = _FIELDS_DICTIONARY[tmpKey]['format']
593
    keys_form_unit[tmpKey]['TUNIT'] = _FIELDS_DICTIONARY[tmpKey]['unit']
594

  
595
#Read the fits table
596
hdulist = pyfits.open(fits_file)
597
fits_header = hdulist[1].header	# HEADER 
598
fits_data = hdulist[1].data 		# DATA 
599

  
600
#Number of columns in FITS table
601
Ncol_fits = int(fits_header['TFIELDS'])
602

  
603
#Number of rows in FITS table
604
Nrows_fits = fits_header['NAXIS2']
605

  
606
print "\n\t\t **** FITS table details ****"
607
print "\t\t Number of columns: %s" % (Ncol_fits)
608
print "\t\t Number of rows: %s" % (Nrows_fits)
609
print "\t\t **** *** *** *** *** *** ***"
610

  
611
#Fits keywords read from the header
612
fits_keywds=[]
613
original_fits_keywds = []
614

  
615
for i in range(Ncol_fits):
616
  original_fits_keywds.append(fits_data.names[i])
617
  fits_keywds.append(fits_data.names[i])
618

  
619
#Find the keywords written in the ASCII file and the corresponding columns in the FITS table...
620
common_keywds=[]
621
commonKeywds_index=[]
622
keywds_to_update=[]
623
for j in range(Ncol_fits):
624
  if fits_keywds[j] in ascii_keywds:
625
    common_keywds.append(fits_keywds[j])
626
    commonKeywds_index.append(j+1)
627

  
628
    #Only the fields with new values will be updated. Also NAME, RA and DEC are allowed to change
629
    keywds_to_update.append(fits_keywds[j])
630

  
631
print "\n\t%s The following keyword(s) will be updated in the FITS table: " % info , keywds_to_update
632

  
633
#...also selecting the NEW keywords defined in the ASCII file...
634
keywds_to_add=[item for item in ascii_keywds if item not in fits_keywds]
635

  
636
print "\n\t%s The following new keyword(s) will be added to the FITS table: " % info , keywds_to_add
637

  
638
#To associate TFORM and TUNIT to each field, first look into _FIELDS_DICTIONARY
639
#If nothing is found ther, ask the user to enter them manually
640
for i in range(len(keywds_to_add)):
641
  if keywds_to_add[i] not in _FIELDS_DICTIONARY:
642
    keys_form_unit[keywds_to_add[i]] = {}
643
    message = "\n%s Please enter the format (\'TFORM\') of the new field \"%s\" (e.g.: 5A, E, L, ...): " % (question, keywds_to_add[i])
644
    keys_form_unit[keywds_to_add[i]]['TFORM'] = raw_input(message)
645
    message = "\n%s Please enter the unit (\'TUNIT\') of the new field \"%s\" (e.g.: None, arcmin, ...): " % (question, keywds_to_add[i])
646
    keys_form_unit[keywds_to_add[i]]['TUNIT'] = raw_input(message)
647
  else:
648
    keys_form_unit[keywds_to_add[i]] = {}
649
    keys_form_unit[keywds_to_add[i]]['TFORM'] = _FIELDS_DICTIONARY[keywds_to_add[i]]['format']
650
    keys_form_unit[keywds_to_add[i]]['TUNIT'] = _FIELDS_DICTIONARY[keywds_to_add[i]]['unit']
651
        
652
  # ...to be appended into the 'fits_keywds' array
653
  fits_keywds.append(keywds_to_add[i])
654

  
655
'''
656
  *** Add the NEW COLUMNS to FITS table ***
657
'''
658

  
659
#Initialize new columns
660
a_tmp = []
661

  
662
coldefs = pyfits.ColDefs(hdulist[1].columns)
663
columns = []
664

  
665
for keys in keywds_to_add:
666
  if keys_form_unit[keys]['TFORM'] == 'E' or keys_form_unit[keys]['TFORM'] == 'D':
667
    a_tmp = [-1.6375E+30] * Nrows_fits # Initialize Float with empty array
668
  elif keys_form_unit[keys]['TFORM'] == 'I':
669
    a_tmp = [-1] * Nrows_fits # Initialize Integer with -1 array
670
  elif keys_form_unit[keys]['TFORM'] == 'L':
671
    a_tmp = [False] * Nrows_fits # Initialize logical with True array
672
  elif keys_form_unit[keys]['TFORM'].find('A') >= 0:
673
    a_tmp = ['Null'] * Nrows_fits
674

  
675
  while True:
676
    #Check between field format and values.
677
    try:
678
      col_tmp = pyfits.Column(name=keys, format=keys_form_unit[keys]['TFORM'], unit=keys_form_unit[keys]['TUNIT'], array=a_tmp)
679
      columns.append(col_tmp)
680
      break
681
    except ValueError:
682
      print bcolors.FAIL+ "\n\t\t*** FORMAT INCONSISTENT WITH DATA ***" + bcolors.ENDC
683
      keys_form_unit[keys]['TFORM'] = raw_input("\n%s Please, enter again the format (\'TFORM\') of the new field \"%s\": " % (question, keys))
684
         
685
'''
686
  *** 1st data UPDATE: new fields added as new columns ***
687
'''
688

  
689
#New for Pyfits > 2.3
690
for i in columns: coldefs.add_col(i)
691
hdulist = pyfits.new_table(coldefs)
692

  
693
#Old Python version
694
#hdulist = pyfits.BinTableHDU.from_columns(coldefs)
695

  
696
fits_data = hdulist.data
697

  
698
'''
699
  *** Object identification via POSITION matching, NAME or INDEX  ***
700
'''
701
match_option = False
702
match_radius = 300.0 # default = 5 arcmin
703

  
704
name_index_fits = ''
705
name_index_ascii = ''
706

  
707
print '\n%s Which method do you want to use for the object matching: by POSITION (1) by NAME (2) or by INDEX (3)?' % question
708
while match_option == False:
709
  message = "\n\t-> Please enter 1, 2 or 3:   "
710
  method = raw_input(message)
711
  if method == '1': 
712
    #Check if RA & DEC are actually in FITS and ASCII tables
713
    if name_ra_key not in fits_data.names or name_dec_key not in fits_data.names or name_ra_key not in ascii_keywds or name_dec_key not in ascii_keywds:
714
      print bcolors.FAIL+ "\n\t>> NO %s and %s found in FITS and ASCII tables: POSITION matching not possible <<" % (name_ra_key, name_dec_key) + bcolors.ENDC
715
    else:
716
      match_option = method
717
      match_radius = float(raw_input('\n\t%s Please enter the match radius (in arcsec): ' % question))
718
  elif method == '2' : match_option = method
719
  elif method == '3' :
720
    check_name_index_fits = False
721
    while check_name_index_fits == False:
722
      name_index_fits = raw_input('\n\t-> Please enter the column name of the INDEX in the FITS file: ')
723
      if name_index_fits not in fits_keywds:
724
	print bcolors.FAIL+ "\n\t*** '%s' NOT in FITS Keywords ***" % name_index_fits+ bcolors.ENDC
725
      else:
726
	check_name_index_fits = True
727
	index_fits = np.array( fits_data[name_index_fits] )
728

  
729
    check_name_index_ascii = False
730
    while check_name_index_ascii == False:
731
      name_index_ascii = raw_input('\n\t-> Please enter the column name of the INDEX in the ASCII file: ')
732
      if name_index_ascii not in ascii_keywds:
733
	print bcolors.FAIL+ "\n\t*** '%s' NOT in ASCII Keywords ***" % name_index_ascii+ bcolors.ENDC
734
      else:
735
	check_name_index_ascii = True
736
	index_ascii = [ (ascii_table[k][j]) for k in range(ascii_table.ncols) if ascii_table[k][0] == name_index_ascii for j in range(1,ascii_table.nrows) ]
737
      
738
    match_option = method
739

  
740
  else: print bcolors.FAIL+ "\n\t*** Wrong option ***"+ bcolors.ENDC
741

  
742
name_fits = np.array(fits_data[name_Name_key])
743
ra_fits = np.array(fits_data[ name_ra_key ])
744
dec_fits = np.array(fits_data[ name_dec_key ])
745

  
746
name_ascii = []
747
ra_ascii = []
748
dec_ascii = []
749

  
750
for k in range(ascii_table.ncols):
751
  if ascii_keywds[k]==name_Name_key:
752
    for j in range(ascii_table.nrows -1): name_ascii.append((ascii_table[k][j+1]).strip())
753
  if ascii_keywds[k]==name_ra_key:
754
    for j in range(ascii_table.nrows -1): ra_ascii.append(float(ascii_table[k][j+1]))
755
  elif ascii_keywds[k]==name_dec_key:
756
    for j in range(ascii_table.nrows -1): dec_ascii.append(float(ascii_table[k][j+1]))
757

  
758
dist_asec = []
759

  
760
#Two arrays with the indexes of the matching objects
761
rowAscii_match = []	# ASCII rows
762
rowFits_match = []	# FITS rows
763

  
764
#Array with the indexes of the NEW objects found in the ASCII file (if any)
765
rowAscii_new = []
766

  
767
method_dict = {
768
  '1' : 'POSITION (dist < %.1f")' % match_radius,
769
  '2' : 'NAME',
770
  '3' : 'INDEX'
771
  }
772

  
773
print "\n\t>> Matching ASCII/FITS tables by %s ...\n" % method_dict[method]
774

  
775
num_tot_matches = 0
776
for j in range(Nrows_fits):
777
  num_multiple_matches = 0
778
  id_matches = []
779
  ra_dec_matches = []
780
  
781
  if match_option == '1':
782
    tmp_idxs_matches = []
783
    tmp_dist_matches = []
784
    
785
    for i in range(Nrows_ascii):
786
      dist_tmp = 3600. * astCoords.calcAngSepDeg(float(ra_fits[j]), float(dec_fits[j]), ra_ascii[i], dec_ascii[i])
787
      if dist_tmp <= match_radius:
788
	tmp_idxs_matches.append(i)
789
	tmp_dist_matches.append(round(dist_tmp,1))
790
	num_tot_matches += 1
791
	num_multiple_matches += 1
792
	
793
    idx_match = 0
794
    if len( tmp_idxs_matches ) > 1:
795
      print bcolors.WARNING+ "\n\t! WARNING ! %i objects found within %.1f arcsec from %s \n" % ( len(tmp_idxs_matches), match_radius, name_fits[j]) + bcolors.ENDC
796
      for idx in range( len(tmp_idxs_matches) ): print '\t%i: %s (dist = %s")' % ( (idx+1, name_ascii[ tmp_idxs_matches[idx]], tmp_dist_matches[idx] ) )
797
      tmp_check = False
798
      while tmp_check == False:
799
	tmp_entry = int(raw_input('\t-> Please enter the number of the matching object: '))
800
	if tmp_entry in range(1, len(tmp_idxs_matches)+1 ): 
801
	  tmp_check = True
802
	  idx_match = tmp_idxs_matches[ tmp_entry - 1 ]
803
	else:
804
	  print bcolors.FAIL+ "\n\t*** Wrong option ***\n"+ bcolors.ENDC
805
	
806
      id_matches.append((name_ascii[idx_match]).strip())
807
      ra_dec_matches.append(ra_ascii[idx_match])
808
      ra_dec_matches.append(dec_ascii[idx_match])
809
      
810
      # NOTE: When the ascii_table is called, the corresponding index is (rowAscii_match + 1) because of the additional line for the HEADER
811
      rowAscii_match.append(idx_match)
812
      rowFits_match.append(j)
813

  
814
    elif len( tmp_idxs_matches ) == 1:
815
      idx_match = tmp_idxs_matches[0]
816
      
817
      id_matches.append((name_ascii[idx_match]).strip())
818
      ra_dec_matches.append(ra_ascii[idx_match])
819
      ra_dec_matches.append(dec_ascii[idx_match])
820
      
821
      # NOTE: When the ascii_table is called, the corresponding index is (rowAscii_match + 1) because of the additional line for the HEADER
822
      rowAscii_match.append(idx_match)
823
      rowFits_match.append(j)
824
      
825
  elif match_option == '2':
826
    for i in range(Nrows_ascii):
827
      if (name_fits[j]).strip() == (name_ascii[i]).strip():
828
	num_multiple_matches += 1
829
	num_tot_matches += 1
830
	if num_multiple_matches > 1: 
831
	
832
	  print '%s Found %i objects with the same name : %s\nAborted.\n' % (error, num_multiple_matches, name_fits[j]); os._exit(0)
833
	  
834
	# NOTE: When the ascii_table is called, the corresponding index is (rowAscii_match + 1) because of the additional line for the HEADER  
835
	rowAscii_match.append(i)
836
	rowFits_match.append(j)
837

  
838
  elif match_option == '3':
839
    for i in range(Nrows_ascii):
840
      if int(index_fits[j]) == int(index_ascii[i]) and (int(index_fits[j]) >= 0 and int(index_ascii[i]) >= 0):
841
	num_tot_matches += 1
842

  
843
	# NOTE: When the ascii_table is called, the corresponding index is (rowAscii_match + 1) because of the additional line for the HEADER
844
	rowAscii_match.append(i)
845
	rowFits_match.append(j)
846
	break
847

  
848
for i in range(Nrows_ascii):
849
  if i not in rowAscii_match: rowAscii_new.append(i) # Rows numbers of the NEW clusters, in the ASCII file
850

  
851
print "\n\t%s Found %s matching clusters between FITS/ASCII table to be UPDATED in the FITS table" % (info, len(rowAscii_match))
852

  
853
print "\n\t%s Found %s NEW clusters in the ASCII table to be ADDED to the FITS table" % (info, len(rowAscii_new))
854

  
855
#Store the names of the common/new clusters
856
idx_name = fits_keywds.index(name_Name_key)
857
clName_fits=[]
858

  
859
for k in range(Nrows_fits):
860
  clName_fits.append(fits_data[k][idx_name])
861

  
862
common_clNames=[]
863
new_clNames=[]
864

  
865
for i, idx in enumerate(rowAscii_match):
866
  common_clNames.append(clName_fits[rowFits_match[i]])
867
  
868
for idx in rowAscii_new:
869
  idx_name = ascii_keywds.index(name_Name_key)
870
  new_clNames.append( ascii_table[idx_name][idx+1] )
871

  
872
#Define the MASS conversion factor, only if it is found in ASCII table:
873
h_factor = 1.0
874
tmp_check = False
875

  
876
mass_in_ascii = set(name_mass_key) & set(ascii_keywds)
877
if mass_in_ascii:
878
  print "\n%s Concerning %s, do you want to:\n\t1) Convert from h70^-1 -> h100^-1\n\t2) Convert from h100^-1 -> h70^-1\n\t3) Keep the original values of the ASCII table" % (question, mass_in_ascii.pop())
879
  while tmp_check == False:
880
    message = "\n\t-> Please enter 1, 2 or 3:   "
881
    h_opt = raw_input(message)
882
    if h_opt == '1': h_factor = 0.7; tmp_check = True
883
    elif h_opt == '2': h_factor = 1./0.7; tmp_check = True
884
    elif h_opt == '3': h_factor = 1.; tmp_check = True
885
    else: print bcolors.FAIL+ "\n\t*** Wrong option ***"+ bcolors.ENDC
886

  
887
newRow_num = Nrows_fits + len(rowAscii_new)
888

  
889
'''
890
  *** 2nd data UPDATE: add the new clusters as new (initially empty) rows ***
891
'''
892
hdulist = pyfits.new_table(hdulist, nrows=newRow_num)
893

  
894
#Add 'CATALOG' to the new clusters (if any)
895
if name_catalog_key in fits_keywds and name_catalog_key not in ascii_keywds and len(rowAscii_new) > 0:
896
  new_catalog = raw_input("\n%s Please enter the value of %s for the new cluster(s): " % (question, name_catalog_key))
897

  
898
  
899
'''
900
  *** Update the PAPER column ***
901
'''
902
paper_flag = False
903
updated_paper_vec = []
904
max_length_paper = 0
905
cnt = 0
906

  
907
#If 'PAPER' is defined in the ASCII table, but it is not in the FITS and there are NO NEW objects, the latter is updated with the former
908
if name_paper_key in ascii_keywds and name_paper_key not in fits_keywds and len(rowAscii_new) == 0:
909
  for j in range(Nrows_fits):
910
    
911
    #Update only those clusters specified in the ASCII table
912
    if j in rowFits_match:
913
      paper_tmp = ascii_table[ascii_keywds.index(name_paper_key)][rowAscii_match[cnt]+1]
914
      cnt += 1
915
    else:
916
      paper_tmp = "Null"
917
    
918
    paper_tmp = remove_duplicated_names(paper_tmp)
919
    updated_paper_vec.append(paper_tmp)
920
    if len(paper_tmp) > max_length_paper: max_length_paper = len(paper_tmp)
921
 
922
#If 'PAPER' is defined in the FITS table, it is updated for the common clusters (and created for the new clusters) with the one defined in the ASCII table.
923
#If no 'PAPER' is found in ASCII, user is asked to enter it manually.
924
elif name_paper_key in fits_keywds:
925
  new_paper_vec = []
926
  col_paper_fits = fits_keywds.index(name_paper_key)
927

  
928
  if name_paper_key in ascii_keywds:
929
    paper_flag = True
930
    for i in range(Nrows_ascii):
931
      new_paper_vec.append( ascii_table[ascii_keywds.index(name_paper_key)][i+1].strip() )
932
  else:
933
    #The new reference is asked to be added manually only if new clusters are found
934
    if len(new_clNames)>0:
935
      tmp_new_paper = raw_input("\n%s Please insert the new reference to add: " % question)
936
      new_paper_vec=[tmp_new_paper for x in range( Nrows_ascii ) ]
937
      paper_flag = True
938
    else:
939
      new_paper_vec=['' for x in range( Nrows_ascii ) ]
940
      
941
  #Update those clusters in common with ASCII and FITS table
942
  for j in range(Nrows_fits):
943
    paper_old = (fits_data[j][col_paper_fits]).strip()
944
    if j in rowFits_match:
945
      if paper_old == "Null":
946
	paper_tmp = new_paper_vec[ rowAscii_match[cnt] ]	#Here the '+1' correction is not necessary because also new_paper_vec[] contains the header line
947
	cnt+=1
948
      else:
949
	paper_tmp = paper_old+"; "+new_paper_vec[ rowAscii_match[cnt] ]	#Here the '+1' correction is not necessary because also new_paper_vec[] contains the header line
950
	cnt += 1
951
    else:
952
      paper_tmp = paper_old
953
  
954
    paper_tmp = remove_duplicated_names(paper_tmp)
955
    updated_paper_vec.append(paper_tmp)
956
    if len(paper_tmp) > max_length_paper: max_length_paper = len(paper_tmp)
957
  
958
#Delete the old 'PAPER' column and update it with a new one defined according to the above case.
959
if name_paper_key in fits_keywds and paper_flag: 
960
  hdulist.columns.del_col(name_paper_key)
961
  
962
  #Add the new PAPER field, as last column
963
  col_tmp = pyfits.Column(name=name_paper_key, format=str(max_length_paper)+'A', unit = 'None', array=updated_paper_vec)
964
  paper_flag = True
965

  
966
if paper_flag:
967
  coldefs = pyfits.ColDefs(hdulist.columns)
968
  coldefs.add_col(col_tmp)
969
  hdulist = pyfits.new_table(coldefs)
970

  
971

  
972
#Update PAPER for common cluster
973
len_ALT_NAME = []
974
new_altName_vec = []
975
old_altName_vec = []
976
new_altName = ""
977
cnt = 0
978

  
979
altName_flag = False
980
name_in_altName = False
981
replace_altName = False
982

  
983
#Handle the NAME/ALT_NAME update in case of position/index matching:
984
if len(common_clNames) > 0:
985
    
986
  if name_altName_key not in ascii_keywds and name_altName_key in fits_keywds:
987
    if name_Name_key in fits_keywds and name_Name_key in ascii_keywds:
988
      answer_check = False
989
      tmp = raw_input("\n\t%s Do you want to add the old clusters' %s listed in FITS table to %s? [y/n]: " % (question, name_Name_key, name_altName_key) )
990
      while answer_check == False:
991
	if tmp in 'yesYES1' and tmp != '': 
992
	  name_in_altName = True
993
	  answer_check = True
994
	elif tmp in 'nN' and tmp != '': answer_check = True
995
	else: tmp = raw_input(bcolors.FAIL+ "\n\t\t*** Please enter a valid answer ***" + bcolors.ENDC + ' [y/n] : ')
996
    
997
    col_altName_fits = fits_keywds.index( name_altName_key )
998

  
999
    for j in range(Nrows_fits):
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff