12
12
import sotodlib .coords .planets as planets
13
13
14
14
from sotodlib .core .flagman import (has_any_cuts , has_all_cut ,
15
- count_cuts ,
15
+ count_cuts , flag_cut_select ,
16
16
sparse_to_ranges_matrix )
17
17
18
18
from .pcore import _Preprocess , _FracFlaggedMixIn
@@ -245,7 +245,6 @@ def plot(self, aman, proc_aman, filename):
245
245
plot_ds_factor = self .plot_cfgs .get ("plot_ds_factor" , 50 ), filename = filename .replace ('{name}' , f'{ ufm } _glitch_signal_diff' ))
246
246
plot_flag_stats (aman , proc_aman [self .glitch_name ], flag_type = 'glitches' , filename = filename .replace ('{name}' , f'{ ufm } _glitch_stats' ))
247
247
248
-
249
248
class FixJumps (_Preprocess ):
250
249
"""
251
250
Repairs the jump heights given a set of jump flags and heights.
@@ -1281,6 +1280,7 @@ class SourceFlags(_Preprocess):
1281
1280
Example config block::
1282
1281
1283
1282
- name : "source_flags"
1283
+ source_flags_name: "my_source_flags"
1284
1284
calc:
1285
1285
mask: {'shape': 'circle',
1286
1286
'xyr': [0, 0, 1.]}
@@ -1290,80 +1290,113 @@ class SourceFlags(_Preprocess):
1290
1290
distance: 0 # max distance of footprint from source in degrees
1291
1291
save: True
1292
1292
select: True # optional
1293
+ select_source: 'jupiter' # list of str or str. If not provided, all sources from center_on are selected.
1294
+ cut: True # True performs cut based on flags
1295
+ kind: 'any' # 'any', 'all', or 'cut'
1296
+ Examples:
1297
+ 1. cut=True, kind='any' → Select detectors with **no** True flags (e.g., for Moon cut).
1298
+ 2. cut=False, kind='any' → Select detectors with **any** True flags (e.g., for planet selection).
1299
+ 3. cut=True, kind=0.4 → Select detectors with <40% of True flags.
1293
1300
1294
1301
.. autofunction:: sotodlib.tod_ops.flags.get_source_flags
1295
1302
"""
1296
1303
name = "source_flags"
1304
+ def __init__ (self , step_cfgs ):
1305
+ self .source_flags_name = step_cfgs .get ('source_flags_name' , 'source_flags' )
1306
+ super ().__init__ (step_cfgs )
1297
1307
1298
1308
def calc_and_save (self , aman , proc_aman ):
1299
- source_list = np . atleast_1d ( self . calc_cfgs . get ( 'center_on' , 'planet' ))
1300
- if source_list == [ ' planet']:
1301
- from sotodlib . coords . planets import SOURCE_LIST
1309
+ from sotodlib . coords . planets import SOURCE_LIST
1310
+ source_name = np . atleast_1d ( self . calc_cfgs . get ( 'center_on' , ' planet'))
1311
+ if 'planet' in source_name :
1302
1312
source_list = [x for x in aman .tags if x in SOURCE_LIST ]
1303
1313
if len (source_list ) == 0 :
1304
1314
raise ValueError ("No tags match source list" )
1315
+ else :
1316
+ source_list = [planets .get_source_list_fromstr (isource ) for isource in source_name ]
1305
1317
1306
1318
# find if source is within footprint + distance
1307
1319
positions = planets .get_nearby_sources (tod = aman , source_list = source_list ,
1308
1320
distance = self .calc_cfgs .get ('distance' , 0 ))
1309
-
1310
1321
source_aman = core .AxisManager (aman .dets , aman .samps )
1311
1322
for p in positions :
1323
+ center_on = planets .get_source_list_fromstr (p [0 ])
1312
1324
source_flags = tod_ops .flags .get_source_flags (aman ,
1313
1325
merge = self .calc_cfgs .get ('merge' , False ),
1314
1326
overwrite = self .calc_cfgs .get ('overwrite' , True ),
1315
1327
source_flags_name = self .calc_cfgs .get ('source_flags_name' , None ),
1316
1328
mask = self .calc_cfgs .get ('mask' , None ),
1317
- center_on = p [ 0 ] ,
1329
+ center_on = center_on ,
1318
1330
res = self .calc_cfgs .get ('res' , None ),
1319
1331
max_pix = self .calc_cfgs .get ('max_pix' , None ))
1320
1332
1321
1333
source_aman .wrap (p [0 ], source_flags , [(0 , 'dets' ), (1 , 'samps' )])
1322
1334
1335
+ # if inv_flag is set, add inverse of source flags
1336
+ if self .calc_cfgs .get ('inv_flag' ):
1337
+ source_aman .wrap (p [0 ]+ '_inv' ,
1338
+ RangesMatrix .from_mask (~ source_flags .mask ()),
1339
+ [(0 , 'dets' ), (1 , 'samps' )])
1340
+
1323
1341
# add sources that were not nearby from source list
1324
- for source in source_list :
1342
+ for source in source_name :
1325
1343
if source not in source_aman ._fields :
1326
1344
source_aman .wrap (source , RangesMatrix .zeros ([aman .dets .count , aman .samps .count ]),
1327
1345
[(0 , 'dets' ), (1 , 'samps' )])
1346
+
1347
+ if self .calc_cfgs .get ('inv_flag' ):
1348
+ source_aman .wrap (source + '_inv' ,
1349
+ RangesMatrix .ones ([aman .dets .count , aman .samps .count ]),
1350
+ [(0 , 'dets' ), (1 , 'samps' )])
1328
1351
1329
1352
self .save (proc_aman , source_aman )
1330
1353
1331
1354
def save (self , proc_aman , source_aman ):
1332
1355
if self .save_cfgs is None :
1333
1356
return
1334
1357
if self .save_cfgs :
1335
- proc_aman .wrap ("source_flags" , source_aman )
1358
+ proc_aman .wrap (self . source_flags_name , source_aman )
1336
1359
1337
1360
def select (self , meta , proc_aman = None , in_place = True ):
1338
1361
if self .select_cfgs is None :
1339
1362
return meta
1340
1363
if proc_aman is None :
1341
1364
source_flags = meta .preprocess .source_flags
1342
1365
else :
1343
- source_flags = proc_aman . source_flags
1366
+ source_flags = proc_aman [ self . source_flags_name ]
1344
1367
1345
- source_list = np .atleast_1d (self .calc_cfgs .get ('center_on' , 'planet' ))
1346
- if source_list == [ 'planet' ] :
1368
+ select_list = np .atleast_1d (self .select_cfgs . get ( "select_source" , self . calc_cfgs .get ('center_on' , 'planet' ) ))
1369
+ if 'planet' in select_list :
1347
1370
from sotodlib .coords .planets import SOURCE_LIST
1348
- source_list = [x for x in aman .tags if x in SOURCE_LIST ]
1349
- if len (source_list ) == 0 :
1371
+ select_list = [x for x in meta .tags if x in SOURCE_LIST ]
1372
+ if len (select_list ) == 0 :
1350
1373
raise ValueError ("No tags match source list" )
1351
1374
1375
+ cuts = self .select_cfgs .get ("cut" , True ) # default of True is for backward compatibility
1376
+ if isinstance (cuts , bool ):
1377
+ cuts = [cuts ]* len (select_list )
1378
+ elif len (cuts ) != len (select_list ):
1379
+ raise ValueError ("Length of cuts must match length of select_source, or just bool" )
1380
+
1381
+ kinds = self .select_cfgs .get ("kind" , 'all' ) # default of 'all' is for backward compatibility
1382
+ if isinstance (kinds , (str , float )):
1383
+ kinds = [kinds ]* len (select_list )
1384
+ elif len (kinds ) != len (select_list ):
1385
+ raise ValueError ("Length of kinds must match length of select_source, or just str" )
1386
+
1352
1387
keep_all = np .ones (meta .dets .count , dtype = bool )
1353
1388
1354
- for source in source_list :
1389
+ for source , kind , cut in zip ( select_list , kinds , cuts ) :
1355
1390
if source in source_flags ._fields :
1356
- keep = ~ has_all_cut (source_flags [source ])
1357
- if in_place :
1358
- meta .restrict ("dets" , meta .dets .vals [keep ])
1359
- source_flags .restrict ("dets" , source_flags .dets .vals [keep ])
1360
- else :
1361
- keep_all &= keep
1391
+ keep_all &= flag_cut_select (source_flags [source ], cut , kind )
1392
+
1362
1393
if in_place :
1394
+ meta .restrict ("dets" , meta .dets .vals [keep_all ])
1395
+ source_flags .restrict ("dets" , source_flags .dets .vals [keep_all ])
1363
1396
return meta
1364
1397
else :
1365
1398
return keep_all
1366
-
1399
+
1367
1400
class HWPAngleModel (_Preprocess ):
1368
1401
"""Apply hwp angle model to the TOD.
1369
1402
@@ -1689,7 +1722,7 @@ def __init__(self, step_cfgs):
1689
1722
1690
1723
super ().__init__ (step_cfgs )
1691
1724
1692
- def process (self , aman , proc_aman ):
1725
+ def process (self , aman , proc_aman , sim = False ):
1693
1726
n_modes = self .process_cfgs .get ('n_modes' )
1694
1727
signal = aman .get (self .signal )
1695
1728
flags = aman .flags .get (self .process_cfgs .get ('source_flags' ))
@@ -2146,10 +2179,33 @@ class CorrectIIRParams(_Preprocess):
2146
2179
"""
2147
2180
name = "correct_iir_params"
2148
2181
2149
- def process (self , aman , proc_aman ):
2182
+ def process (self , aman , proc_aman , sim = False ):
2150
2183
from sotodlib .obs_ops import correct_iir_params
2151
2184
correct_iir_params (aman )
2152
2185
2186
+ class TrimFlagEdge (_Preprocess ):
2187
+ """Trim edge until given flags of all detectors are False
2188
+ To find first and last sample id that has False (i.e., no flags applied) for all detectors.
2189
+ This is for avoiding glitchfill problem for data whose edge has flags of True.
2190
+
2191
+ Example config block::
2192
+
2193
+ - name: "trim_flag_edge"
2194
+ process:
2195
+ flags: "pca_exclude"
2196
+
2197
+ .. autofunction:: sotodlib.core.flagman.find_common_edge_idx
2198
+ """
2199
+ name = 'trim_flag_edge'
2200
+
2201
+ def process (self , aman , proc_aman , sim = False ):
2202
+ flags = aman .flags .get (self .process_cfgs .get ('flags' ))
2203
+ trimst , trimen = core .flagman .find_common_edge_idx (flags )
2204
+ aman .restrict ('samps' , (aman .samps .offset + trimst ,
2205
+ aman .samps .offset + trimen ))
2206
+ proc_aman .restrict ('samps' , (proc_aman .samps .offset + trimst ,
2207
+ proc_aman .samps .offset + trimen ))
2208
+
2153
2209
_Preprocess .register (SplitFlags )
2154
2210
_Preprocess .register (SubtractT2P )
2155
2211
_Preprocess .register (EstimateT2P )
@@ -2193,3 +2249,4 @@ def process(self, aman, proc_aman):
2193
2249
_Preprocess .register (PointingModel )
2194
2250
_Preprocess .register (BadSubscanFlags )
2195
2251
_Preprocess .register (CorrectIIRParams )
2252
+ _Preprocess .register (TrimFlagEdge )
0 commit comments