casa
$Rev:20696$
|
00001 #! /usr/bin/env python 00002 # The above is for running the doctest, not normal use. 00003 00004 """ 00005 A set of functions for manipulating spw:chan selection strings. 00006 00007 If this is run from a shell (i.e. not in casapy), doctest will be used to run 00008 several unit tests from the doc strings, including the one below: 00009 00010 Example: 00011 >>> from update_spw import update_spw 00012 >>> update_spw('0~2,5', None)[0] 00013 '0~2,3' 00014 >>> update_spw('0~2,5', None)[1]['5'] # doctest warning! dicts don't always print out in the same order! 00015 '3' 00016 """ 00017 00018 import copy 00019 import os 00020 #from taskinit import mstool 00021 from casac import * 00022 from taskinit import ms 00023 00024 def update_spw(spw, spwmap=None): 00025 """ 00026 Given an spw:chan selection string, return what it should be after the spws 00027 have been remapped (i.e. by split), and a map from input to output spws 00028 (spwmap). It does not change spw OR the *channels* part of the output spw 00029 string! (See update_spwchan) 00030 00031 If given, spwmap will be used as a dictionary from (string) input spw to 00032 (string) output spws. Otherwise it will be freshly calculated. Supplying 00033 spwmap doesn't just save work: it is also necessary for chaining 00034 update_spw() calls when the first selection includes more spws than the 00035 subsequent one(s). HOWEVER, if given, spwmap must have slots for all the 00036 spws that will appear in the output MS, i.e. it can't be grown once made. 00037 00038 Examples: 00039 >>> from update_spw import update_spw 00040 >>> myfitspw, spws = update_spw('0~3,5;6:1~7;11~13', None) 00041 >>> myfitspw 00042 '0~3,4;5:1~7;11~13' 00043 >>> myspw = update_spw('1,5;6:8~10', spws)[0] 00044 >>> myspw # not '0,1,2:8~10' 00045 '1,4;5:8~10' 00046 >>> update_spw('0~3,5;6:1~7;11~13,7~9:0~3,11,7~8:6~8', None)[0] 00047 '0~3,4;5:1~7;11~13,6~8:0~3,9,6~7:6~8' 00048 00049 # Let's say we want updates of both fitspw and spw, but fitspw and spw 00050 # are disjoint (in spws). 00051 >>> fitspw = '1~10:5~122,15~22:5~122' 00052 >>> spw = '6~14' 00053 00054 # Initialize spwmap with the union of them. 00055 >>> spwmap = update_spw(join_spws(fitspw, spw), None)[1] 00056 00057 >>> myfitspw = update_spw(fitspw, spwmap)[0] 00058 >>> myfitspw 00059 '0~9:5~122,14~21:5~122' 00060 >>> myspw = update_spw(spw, spwmap)[0] 00061 >>> myspw 00062 '5~13' 00063 >>> myspw = update_spw('0,1,3;5~8:20~30;44~50^2', None)[0] 00064 >>> myspw 00065 '0,1,2;3~6:20~30;44~50^2' 00066 """ 00067 # Blank is valid. Blank is good. 00068 if not spw: 00069 return '', {} 00070 00071 # A list of [spw, chan] pairs. The chan parts will not be changed. 00072 spwchans = [] 00073 00074 make_spwmap = False 00075 if not spwmap: 00076 spwmap = {} 00077 make_spwmap = True 00078 spws = set([]) 00079 00080 # Because ; means different things when it separates spws and channel 00081 # ranges, I can't think of a better way to construct spwchans than an 00082 # explicit state machine. (But $spws_alone =~ s/:[^,]+//g;) 00083 inspw = True # until a : is encountered. 00084 spwgrp = '' 00085 chagrp = '' 00086 00087 def store_spwchan(sstr, cstr): 00088 spwchans.append([sstr, cstr]) 00089 if make_spwmap: 00090 for sgrp in sstr.split(';'): 00091 if sgrp.find('~') > -1: 00092 start, end = map(int, sgrp.split('~')) 00093 spws.update(range(start, end + 1)) 00094 else: 00095 spws.add(int(sgrp)) 00096 00097 for c in spw: 00098 if c == ',': # Start new [spw, chan] pair. 00099 # Store old one. 00100 store_spwchan(spwgrp, chagrp) 00101 00102 # Initialize new one. 00103 spwgrp = '' 00104 chagrp = '' 00105 inspw = True 00106 elif c == ':': 00107 inspw = False 00108 elif inspw: 00109 spwgrp += c 00110 else: 00111 chagrp += c 00112 # Store final [spw, chan] pair. 00113 store_spwchan(spwgrp, chagrp) 00114 00115 # print "spwchans =", spwchans 00116 # print "spws =", spws 00117 00118 # Update spw (+ fitspw) 00119 if make_spwmap: 00120 i = 0 00121 for s in sorted(spws): 00122 spwmap[str(s)] = str(i) 00123 i += 1 00124 outstr = '' 00125 for sc in spwchans: 00126 sgrps = sc[0].split(';') 00127 for sind in xrange(len(sgrps)): 00128 sgrp = sgrps[sind] 00129 if sgrp.find('~') > -1: 00130 start, end = sgrp.split('~') 00131 sgrps[sind] = spwmap[start] + '~' + spwmap[end] 00132 else: 00133 sgrps[sind] = spwmap[sgrp] 00134 outstr += ';'.join(sgrps) 00135 if sc[1]: 00136 outstr += ':' + sc[1] 00137 outstr += ',' 00138 00139 return outstr.rstrip(','), spwmap # discard final comma. 00140 00141 def spwchan_to_ranges(vis, spw): 00142 """ 00143 Returns the spw:chan selection string spw as a dict of channel selection 00144 ranges for vis, keyed by spectral window ID. 00145 00146 The ranges are stored as tuples of (start channel, 00147 end channel (inclusive!), 00148 step). 00149 00150 Note that '' returns an empty set! Use '*' to select everything! 00151 00152 Example: 00153 >>> from update_spw import spwchan_to_ranges 00154 >>> selranges = spwchan_to_ranges('uid___A002_X1acc4e_X1e7.ms', '7:10~20^2;40~55') 00155 ValueError: spwchan_to_ranges() does not support multiple channel ranges per spw. 00156 >>> selranges = spwchan_to_ranges('uid___A002_X1acc4e_X1e7.ms', '0~1:1~3,5;7:10~20^2') 00157 >>> selranges 00158 {0: (1, 3, 1), 1: (1, 3, 1), 5: (10, 20, 2), 7: (10, 20, 2)} 00159 """ 00160 selarr = ms.msseltoindex(vis, spw=spw)['channel'] 00161 nspw = selarr.shape[0] 00162 selranges = {} 00163 for s in xrange(nspw): 00164 if selranges.has_key(selarr[s][0]): 00165 raise ValueError, 'spwchan_to_ranges() does not support multiple channel ranges per spw.' 00166 selranges[selarr[s][0]] = tuple(selarr[s][1:]) 00167 return selranges 00168 00169 def spwchan_to_sets(vis, spw): 00170 """ 00171 Returns the spw:chan selection string spw as a dict of sets of selected 00172 channels for vis, keyed by spectral window ID. 00173 00174 Note that '' returns an empty set! Use '*' to select everything! 00175 00176 Example (16.ms has spws 0 and 1 with 16 chans each): 00177 >>> from update_spw import spwchan_to_sets 00178 >>> vis = casa['dirs']['data'] + '/regression/unittest/split/unordered_polspw.ms' 00179 >>> spwchan_to_sets(vis, '0:0') 00180 {0: set([0])} 00181 >>> selsets = spwchan_to_sets(vis, '1:1~3;5~9^2,9') # 9 is a bogus spw. 00182 >>> selsets 00183 {1: [1, 2, 3, 5, 7, 9]} 00184 >>> spwchan_to_sets(vis, '1:1~3;5~9^2,8') 00185 {1: set([1, 2, 3, 5, 7, 9]), 8: set([0])} 00186 >>> spwchan_to_sets(vis, '') 00187 {} 00188 """ 00189 if not spw: # ms.msseltoindex(vis, spw='')['channel'] returns a 00190 return {} # different kind of empty array. Skip it. 00191 00192 # Currently distinguishing whether or not vis is a valid MS from whether it 00193 # just doesn't have all the channels in spw is a bit crude. Sanjay is 00194 # working on adding some flexibility to ms.msseltoindex. 00195 if not os.path.isdir(vis): 00196 raise ValueError, str(vis) + ' is not a valid MS.' 00197 00198 sets = {} 00199 try: 00200 scharr = ms.msseltoindex(vis, spw=spw)['channel'] 00201 for scr in scharr: 00202 if not sets.has_key(scr[0]): 00203 sets[scr[0]] = set([]) 00204 00205 # scr[2] is the last selected channel. Bump it up for range(). 00206 scr[2] += 1 00207 sets[scr[0]].update(range(*scr[1:])) 00208 except: 00209 # spw includes channels that aren't in vis, so it needs to be trimmed 00210 # down to make ms.msseltoindex happy. 00211 allrec = ms.msseltoindex(vis, spw='*') 00212 #print "Trimming", spw 00213 spwd = spw_to_dict(spw, {}, False) 00214 for s in spwd: 00215 if s in allrec['spw']: 00216 endchan = allrec['channel'][s, 2] 00217 if not sets.has_key(s): 00218 sets[s] = set([]) 00219 if spwd[s] == '': 00220 # We need to get the spw's # of channels without using 00221 # ms.msseltoindex. 00222 mytb = casac.table() 00223 mytb.open(vis + '/SPECTRAL_WINDOW') 00224 spwd[s] = range(mytb.getcell('NUM_CHAN', s)) 00225 mytb.close() 00226 sets[s].update([c for c in spwd[s] if c <= endchan]) 00227 return sets 00228 00229 def set_to_chanstr(chanset, totnchan=None): 00230 """ 00231 Essentially the reverse of expand_tilde. Given a set or list of integers 00232 chanset, returns the corresponding string form. It will not use non-unity 00233 steps (^) if multiple ranges (;) are necessary, but it will use ^ if it 00234 helps to eliminate any ;s. 00235 00236 totnchan: the total number of channels for the input spectral window, used 00237 to abbreviate the return string. 00238 00239 It returns '' for the empty set and '*' if 00240 00241 Examples: 00242 >>> from update_spw import set_to_chanstr 00243 >>> set_to_chanstr(set([0, 1, 2, 4, 5, 6, 7, 9, 11, 13])) 00244 '0~2;4~7;9;11;13' 00245 >>> set_to_chanstr(set([7, 9, 11, 13])) 00246 '7~13^2' 00247 >>> set_to_chanstr(set([7, 9])) 00248 '7~9^2' 00249 >>> set_to_chanstr([0, 1, 2]) 00250 '0~2' 00251 >>> set_to_chanstr([0, 1, 2], 3) 00252 '*' 00253 >>> set_to_chanstr([0, 1, 2, 6], 3) 00254 '*' 00255 >>> set_to_chanstr([0, 1, 2, 6]) 00256 '0~2;6' 00257 >>> set_to_chanstr([1, 2, 4, 5, 6, 7, 8, 9, 10, 11], 12) 00258 '1~2;4~11' 00259 """ 00260 if totnchan: 00261 mylist = [c for c in chanset if c < totnchan] 00262 else: 00263 mylist = list(chanset) 00264 00265 if totnchan == len(mylist): 00266 return '*' 00267 00268 mylist.sort() 00269 00270 retstr = '' 00271 if len(mylist) > 1: 00272 # Check whether the same step can be used throughout. 00273 step = mylist[1] - mylist[0] 00274 samestep = True 00275 for i in xrange(2, len(mylist)): 00276 if mylist[i] - mylist[i - 1] != step: 00277 samestep = False 00278 break 00279 if samestep: 00280 retstr = str(mylist[0]) + '~' + str(mylist[-1]) 00281 if step > 1: 00282 retstr += '^' + str(step) 00283 else: 00284 sc = mylist[0] 00285 oldc = sc 00286 retstr = str(sc) 00287 nc = len(mylist) 00288 for i in xrange(1, nc): 00289 cc = mylist[i] 00290 if (cc > oldc + 1) or (i == nc - 1): 00291 if (i == nc - 1) and (cc == oldc + 1): 00292 retstr += '~' + str(cc) 00293 else: 00294 if oldc != sc: 00295 retstr += '~' + str(oldc) 00296 retstr += ';' + str(cc) 00297 sc = cc 00298 oldc = cc 00299 elif len(mylist) > 0: 00300 retstr = str(mylist[0]) 00301 return retstr 00302 00303 def sets_to_spwchan(spwsets, nchans={}): 00304 """ 00305 Returns a spw:chan selection string for a dict of sets of selected 00306 channels keyed by spectral window ID. 00307 00308 nchans is a dict of the total number of channels keyed by spw, used to 00309 abbreviate the return string. 00310 00311 Examples: 00312 >>> from update_spw import sets_to_spwchan 00313 >>> # Use nchans to get '1' instead of '1:0~3'. 00314 >>> sets_to_spwchan({1: [0, 1, 2, 3]}, {1: 4}) 00315 '1' 00316 >>> sets_to_spwchan({1: set([1, 2, 3, 5, 7, 9]), 8: set([0])}) 00317 '1:1~3;5;7;9,8:0' 00318 >>> sets_to_spwchan({0: set([4, 5, 6]), 1: [4, 5, 6], 2: [4, 5, 6]}) 00319 '0~2:4~6' 00320 >>> sets_to_spwchan({0: [4], 1: [4], 3: [0, 1], 4: [0, 1], 7: [0, 1]}, {3: 2, 4: 2, 7: 2}) 00321 '0~1:4,3~4,7' 00322 """ 00323 # Make a list of spws for each channel selection. 00324 csd = {} 00325 for s in spwsets: 00326 # Convert the set of channels to a string. 00327 if spwsets[s]: 00328 cstr = set_to_chanstr(spwsets[s], nchans.get(s)) 00329 00330 if cstr: 00331 if not csd.has_key(cstr): 00332 csd[cstr] = [] 00333 csd[cstr].append(s) 00334 00335 # Now convert those spw lists into strings, inverting as we go so the final 00336 # string can be sorted by spw: 00337 scd = {} 00338 while csd: 00339 cstr, slist = csd.popitem() 00340 slist.sort() 00341 startspw = slist[0] 00342 oldspw = startspw 00343 sstr = str(startspw) 00344 nselspw = len(slist) 00345 for sind in xrange(1, nselspw): 00346 currspw = slist[sind] 00347 if (currspw > oldspw + 1) or (sind == nselspw - 1): 00348 if currspw > oldspw + 1: 00349 if oldspw != startspw: 00350 sstr += '~' + str(oldspw) 00351 sstr += ';' + str(currspw) 00352 startspw = currspw 00353 else: # The range has come to an end on the last spw. 00354 sstr += '~' + str(currspw) 00355 oldspw = currspw 00356 scd[sstr] = cstr 00357 spwgrps = sorted(scd.keys()) 00358 00359 # Finally stitch together the final string. 00360 scstr = '' 00361 for sstr in spwgrps: 00362 scstr += sstr 00363 if scd[sstr] != '*': 00364 scstr += ':' + scd[sstr] 00365 scstr += ',' 00366 return scstr.rstrip(',') 00367 00368 def update_spwchan(vis, sch0, sch1, truncate=False, widths={}): 00369 """ 00370 Given an spw:chan selection string sch1, return what it must be changed to 00371 to get the same result if used with the output of split(vis, spw=sch0). 00372 00373 '' is taken to mean '*' in the input but NOT the output! For the output 00374 '' means sch0 and sch1 do not intersect. 00375 00376 truncate: If True and sch0 only partially overlaps sch1, return the update 00377 of the intersection. 00378 If (False and sch0 does not cover sch1), OR 00379 there is no intersection, raises a ValueError. 00380 00381 widths is a dictionary of averaging widths (default 1) for each spw. 00382 00383 Examples: 00384 >>> from update_spw import update_spwchan 00385 >>> newspw = update_spwchan('anything.ms', 'anything', 'anything') 00386 >>> newspw 00387 '*' 00388 >>> vis = casa['dirs']['data'] + '/regression/unittest/split/unordered_polspw.ms' 00389 >>> update_spwchan(vis, '0~1:1~3,5;7:10~20^2', '0~1:2~3,5;7:12~18^2') 00390 '0~1:1~2,2~3:1~4' 00391 >>> update_spwchan(vis, '7', '3') 00392 ValueError: '3' is not a subset of '7'. 00393 >>> update_spwchan(vis, '7:10~20^2', '7:12~18^3') 00394 ValueError: '7:12~18^3' is not a subset of '7:10~20^2'. 00395 >>> update_spwchan(vis, '7:10~20^2', '7:12~18^3', truncate=True) 00396 '0:1~4^3' 00397 >>> update_spwchan(vis, '7:10~20^2', '7:12~18^3', truncate=True, widths={7: 2}) 00398 '0:0~2^2' 00399 """ 00400 # Convert '' to 'select everything'. 00401 if not sch0: 00402 sch0 = '*' 00403 if not sch1: 00404 sch1 = '*' 00405 00406 # Short circuits 00407 if sch1 == '*': 00408 return '*' 00409 elif sch1 in (sch0, '*'): 00410 return '*' 00411 00412 sch0sets = spwchan_to_sets(vis, sch0) 00413 sch1sets = spwchan_to_sets(vis, sch1) 00414 00415 outsets = {} 00416 outspw = 0 00417 s0spws = sorted(sch0sets.keys()) 00418 s1spws = sorted(sch1sets.keys()) 00419 ns0spw = len(s0spws) 00420 nchans = {} 00421 for s in s1spws: 00422 if s in s0spws: 00423 s0 = sch0sets[s] 00424 s1 = sch1sets[s] 00425 00426 # Check for and handle (throw or dispose) channels in sch1 that aren't in 00427 # sch0. 00428 if s1.difference(s0): 00429 if truncate: 00430 s1.intersection_update(s0) 00431 if not s1: 00432 raise ValueError, "'%s' does not overlap '%s'." % (sch1, sch0) 00433 else: 00434 raise ValueError, "'%s' is not a subset of '%s'." % (sch1, sch0) 00435 00436 # Adapt s1 for a post-s0 world. 00437 s0list = sorted(list(s0)) 00438 s1list = sorted(list(s1)) 00439 outchan = 0 00440 nc0 = len(s0list) 00441 for s1ind in xrange(len(s1list)): 00442 while (outchan < nc0) and (s0list[outchan] < s1list[s1ind]): 00443 outchan += 1 00444 if outchan == nc0: # Shouldn't happen 00445 outchan -= 1 00446 s1list[s1ind] = outchan / widths.get(s, 1) 00447 00448 # Determine outspw. 00449 while (outspw < ns0spw) and (s0spws[outspw] < s): 00450 outspw += 1 00451 if outspw == ns0spw: # Shouldn't happen 00452 outspw -= 1 00453 00454 outsets[outspw] = set(s1list) 00455 00456 # Get the number of channels per spw that are selected by s0. 00457 nchans[outspw] = len(s0) 00458 elif not truncate: 00459 raise ValueError, str(s) + ' is not a selected spw of ' + sch0 00460 00461 return sets_to_spwchan(outsets, nchans) 00462 00463 def expand_tilde(tstr, conv_multiranges=False): 00464 """ 00465 Expands a string like '8~11' to [8, 9, 10, 11]. 00466 Returns '*' if tstr is ''! 00467 00468 conv_multiranges: If True, '*' will be returned if tstr contains ';'. 00469 (split can't yet handle multiple channel ranges per spw.) 00470 00471 Examples: 00472 >>> from update_spw import expand_tilde 00473 >>> expand_tilde('8~11') 00474 [8, 9, 10, 11] 00475 >>> expand_tilde(None) 00476 '*' 00477 >>> expand_tilde('3~7^2;9~11') 00478 [3, 5, 7, 9, 10, 11] 00479 >>> expand_tilde('3~7^2;9~11', True) 00480 '*' 00481 """ 00482 tstr = str(tstr) # Allows bare ints. 00483 if (not tstr) or (conv_multiranges and tstr.find(';') > -1): 00484 return '*' 00485 00486 tstr = tstr.replace("'", '') # Dequote 00487 tstr = tstr.replace('"', '') 00488 00489 numset = set([]) 00490 00491 for numrang in tstr.split(';'): 00492 step = 1 00493 try: 00494 if numrang.find('~') > -1: 00495 if numrang.find('^') > -1: 00496 numrang, step = numrang.split('^') 00497 step = int(step) 00498 start, end = map(int, numrang.split('~')) 00499 else: 00500 start = int(numrang) 00501 end = start 00502 except: 00503 raise ValueError, 'numrang = ' + numrang + ', tstr = ' + tstr + ', conv_multiranges = ' + str(conv_multiranges) 00504 numset.update(range(start, end + 1, step)) 00505 return sorted(list(numset)) 00506 00507 def spw_to_dict(spw, spwdict={}, conv_multiranges=True): 00508 """ 00509 Expand an spw:chan string to {s0: [s0chans], s1: [s1chans, ...], ...} 00510 where s0, s1, ... are integers for _each_ selected spw, and s0chans is a 00511 set of selected chans (as integers) for s0. '' instead of a channel set 00512 means that all of the channels are selected. 00513 00514 The spw:chan dict is unioned with spwdict. 00515 00516 Returning an empty dict means everything should be selected (i.e. spw = ''). 00517 (split can't yet handle multiple channel ranges per spw.) 00518 00519 conv_multiranges: If True, any spw with > 1 channel range selected will 00520 have ALL of its channels selected. 00521 (split can't yet handle multiple channel ranges per spw.) 00522 00523 Examples: 00524 >>> from update_spw import spw_to_dict 00525 >>> spw_to_dict('', {}) 00526 {} 00527 >>> spw_to_dict('6~8:2~5', {})[6] 00528 set([2, 3, 4, 5]) 00529 >>> spw_to_dict('6~8:2~5', {})[8] 00530 set([2, 3, 4, 5]) 00531 >>> spw_to_dict('6~8:2~5', {6: ''})[6] 00532 '' 00533 >>> spw_to_dict('6~8:2~5', {6: '', 7: set([1, 7])})[7] 00534 set([1, 2, 3, 4, 5, 7]) 00535 >>> spw_to_dict('7', {6: '', 7: set([1, 7])})[7] 00536 '' 00537 >>> spw_to_dict('7:123~127;233~267', {6: '', 7: set([1, 7])})[7] # Multiple chan ranges 00538 '' 00539 >>> spw_to_dict('5,7:123~127;233~267', {6: '', 7: set([1, 7])})[5] 00540 '' 00541 >>> spw_to_dict('5:3~5,7:123~127;233~267', {6: '', 7: set([1, 7])})[5] 00542 set([3, 4, 5]) 00543 """ 00544 if not spw: 00545 return {} 00546 00547 myspwdict = copy.deepcopy(spwdict) 00548 00549 # Because ; means different things when it separates spws and channel 00550 # ranges, I can't think of a better way to construct myspwdict than an 00551 # explicit state machine. (But $spws_alone =~ s/:[^,]+//g;) 00552 inspw = True # Must start with an spw. 00553 spwgrp = '' 00554 chagrp = '' 00555 00556 def enter_ranges(spwg, chag): 00557 spwrange = expand_tilde(spwg) 00558 if spwrange == '*': # This shouldn't happen. 00559 return {} 00560 else: 00561 charange = expand_tilde(chag, conv_multiranges) 00562 for s in spwrange: 00563 if charange == '*': 00564 myspwdict[s] = '' 00565 else: 00566 if not myspwdict.has_key(s): 00567 myspwdict[s] = set([]) 00568 if myspwdict[s] != '': 00569 myspwdict[s].update(charange) 00570 00571 for c in spw: 00572 if c == ',' or (inspw and c == ';'): # Start new [spw, chan] pair. 00573 # Store old one. 00574 enter_ranges(spwgrp, chagrp) 00575 00576 # Initialize new one. 00577 spwgrp = '' 00578 chagrp = '' 00579 inspw = True 00580 elif c == ':': 00581 inspw = False 00582 elif inspw: 00583 spwgrp += c 00584 else: 00585 chagrp += c 00586 00587 # Store final [spw, chan] pair. 00588 enter_ranges(spwgrp, chagrp) 00589 return myspwdict 00590 00591 def join_spws(spw1, spw2, span_semicolon=True): 00592 """ 00593 Returns the union of spw selection strings spw1 and spw2. 00594 00595 span_semicolon (default True): If True, for any spws 00596 that have > 1 channel range, the entire spw will be selected. 00597 00598 Examples: 00599 >>> from update_spw import join_spws 00600 >>> join_spws('0~2:3~5,3:9~13', '') 00601 '' 00602 >>> join_spws('0~2:3~5,3:9~13', '1~3:4~7') 00603 '0:3~5,1~2:3~7,3' 00604 >>> join_spws('1~10:5~122,15~22:5~122', '1~10:5~122,15~22:5~122') 00605 '1~10:5~122,15~22:5~122' 00606 >>> join_spws('', '') 00607 '' 00608 >>> join_spws('1~10:5~122,15~22:5~122', '0~21') 00609 '0~21,22:5~122' 00610 """ 00611 if not spw1 or not spw2: 00612 return '' 00613 00614 spwdict = spw_to_dict(spw1, {}) 00615 spwdict = spw_to_dict(spw2, spwdict) 00616 00617 res = '' 00618 # Convert channel sets to strings 00619 for s in spwdict: 00620 cstr = '' 00621 if isinstance(spwdict[s], set): 00622 cstr = set_to_chanstr(spwdict[s]) 00623 if span_semicolon and ';' in cstr: 00624 cstr = '' 00625 spwdict[s] = cstr 00626 00627 # If consecutive spws have the same channel selection, merge them. 00628 slist = spwdict.keys() 00629 slist.sort() 00630 res = str(slist[0]) 00631 laststart = 0 00632 for i in xrange(1, len(slist)): 00633 # If consecutive spws have the same channel list, 00634 if slist[i] == slist[i - 1] + 1 and spwdict[slist[i]] == spwdict[slist[i - 1]]: 00635 if slist[i] == slist[laststart] + 1: 00636 res += '~' # Continue the spw range. 00637 else: # Terminate it and start a new one. 00638 if res[-1] == '~': # if start != end 00639 res += str(slist[i - 1]) 00640 if spwdict[slist[i - 1]] != '': # Add channel range, if any. 00641 res += ':' + spwdict[slist[i - 1]] 00642 res += ',' + str(slist[i]) 00643 laststart = i 00644 if res[-1] == '~': # Finish the last range if it is dangling. 00645 res += str(slist[-1]) 00646 if spwdict[slist[-1]] != '': # Add channel range, if any. 00647 res += ':' + spwdict[slist[-1]] 00648 return res 00649 00650 def intersect_spws(spw1, spw2): 00651 """ 00652 Almost the opposite of join_spws(), this returns the list of spws that the 00653 spw:chan selection strings spw1 and spw2 have in common. Unlike join_spws(), 00654 channel ranges are ignored. '' in the input counts as 'select everything', 00655 so the intersection of '' with anything is anything. If the intersection 00656 really is everything, '' is returned instead of a set. 00657 00658 Examples: 00659 >>> from update_spw import intersect_spws 00660 >>> intersect_spws('0~2:3~5,3:9~13', '') 00661 set([0, 1, 2, 3]) 00662 >>> intersect_spws('0~2:3~5,3:9~13', '0~2:7~9,5') 00663 set([0, 1, 2]) 00664 >>> intersect_spws('0~2:3~5;10~13,3:9~13', '0~2:7~9,5') 00665 set([0, 1, 2]) 00666 >>> intersect_spws('0~2:3~5,3:9~13', '10~12:7~9,5') # Empty set 00667 set([]) 00668 >>> intersect_spws('', '') # Everything 00669 '' 00670 """ 00671 if spw1 == '': 00672 if spw2 == '': 00673 return '' # intersection('', '') = '' 00674 else: # intersection('', spw2) = spw2 00675 return set(spw_to_dict(spw2, {}).keys()) # Just the spws, no chan ranges 00676 elif spw2 == '': # intersection('', spw1) = spw1 00677 return set(spw_to_dict(spw1, {}).keys()) # Just the spws, no chan ranges 00678 else: 00679 spwset1 = set(spw_to_dict(spw1, {}).keys()) # spws are the keys, chan 00680 spwset2 = set(spw_to_dict(spw2, {}).keys()) # ranges are the values. 00681 return spwset1.intersection(spwset2) 00682 00683 def subtract_spws(spw1, spw2): 00684 """ 00685 Returns the set of spws of spw selection string spw1 that are not in spw2. 00686 Like intersect_spws(), this intentionally ignores channel ranges. It 00687 assumes that spw1 and spw2 refer to the same MS (this only matters for ''). 00688 subtract_spws('', '0~5') is a tough case: it is impossible to know whether 00689 '' is equivalent to '0~5' without reading the MS's SPECTRAL_WINDOW 00690 subtable, so it returns 'UNKNOWN'. 00691 00692 Examples: 00693 >>> from update_spw import subtract_spws 00694 >>> subtract_spws('0~2:3~5,3:9~13', '') # Anything - Everything 00695 set([]) 00696 >>> subtract_spws('0~2:3~5,3:9~13', '0~2:7~9,5') 00697 set([3]) 00698 >>> subtract_spws('', '0~2:7~9,5') # Everything - Something 00699 'UNKNOWN' 00700 >>> subtract_spws('0~2,3:9~13', '4~7:7') # Something - Something Else 00701 set([0, 1, 2, 3]) 00702 >>> subtract_spws('', '') # Everything - Everything 00703 set([]) 00704 """ 00705 if spw1 == '': 00706 if spw2 == '': 00707 return set([]) 00708 else: 00709 return 'UNKNOWN' 00710 elif spw2 == '': 00711 return set([]) 00712 else: 00713 spwset1 = set(spw_to_dict(spw1, {}).keys()) # spws are the keys, chan 00714 spwset2 = set(spw_to_dict(spw2, {}).keys()) # ranges are the values. 00715 return spwset1.difference(spwset2) 00716 00717 if __name__ == '__main__': 00718 import doctest, sys 00719 doctest.testmod(verbose=True)