• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

desihub / desispec / 8334186034

18 Mar 2024 10:00PM UTC coverage: 28.093% (+3.0%) from 25.113%
8334186034

Pull #2187

github

akremin
only complain about existing jobs if they failed
Pull Request #2187: Introduce desi_proc_night to unify and simplify processing scripts

753 of 1123 new or added lines in 21 files covered. (67.05%)

1066 existing lines in 11 files now uncovered.

13160 of 46844 relevant lines covered (28.09%)

0.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/py/desispec/scripts/proc.py
1
"""
2
desispec.scripts.proc
3
=====================
4

5
One stop shopping for processing a DESI exposure
6

7
Examples at NERSC::
8

9
    # ARC: 18 min on 2 nodes
10
    time srun -N 2 -n 60 -C haswell -t 25:00 --qos realtime desi_proc --mpi -n 20191029 -e 22486
11

12
    # FLAT: 13 min
13
    time srun -n 20 -N 1 -C haswell -t 15:00 --qos realtime desi_proc --mpi -n 20191029 -e 22487
14

15
    # TWILIGHT: 8min
16
    time srun -n 20 -N 1 -C haswell -t 15:00 --qos realtime desi_proc --mpi -n 20191029 -e 22497
17

18
    # SKY: 11 min
19
    time srun -n 20 -N 1 -C haswell -t 15:00 --qos realtime desi_proc --mpi -n 20191029 -e 22536
20

21
    # ZERO: 2 min
22
    time srun -n 20 -N 1 -C haswell -t 15:00 --qos realtime desi_proc --mpi -n 20191029 -e 22561
23
"""
24

25
import time, datetime
×
26
start_imports = time.time()
×
27

28
#- enforce a batch-friendly matplotlib backend
29
from desispec.util import set_backend
×
30
set_backend()
×
31

32
import sys, os, argparse, re
×
33
import subprocess
×
34
from copy import deepcopy
×
35

36
import numpy as np
×
37
import fitsio
×
38
from astropy.io import fits
×
39

40
from astropy.table import Table,vstack
×
41

42
import glob
×
43
import desiutil.timer
×
44
import desispec.io
×
45
from desispec.io import findfile, replace_prefix, shorten_filename, get_readonly_filepath
×
46
from desispec.io.util import create_camword, decode_camword, parse_cameras
×
47
from desispec.io.util import validate_badamps, get_tempfilename, relsymlink
×
48
from desispec.calibfinder import findcalibfile,CalibFinder,badfibers
×
49
from desispec.fiberflat import apply_fiberflat
×
50
from desispec.sky import subtract_sky
×
51
from desispec.util import runcmd
×
52
import desispec.scripts.assemble_fibermap
×
53
import desispec.scripts.preproc
×
54
import desispec.scripts.inspect_dark
×
55
import desispec.scripts.trace_shifts
×
56
import desispec.scripts.interpolate_fiber_psf
×
57
import desispec.scripts.extract
×
58
import desispec.scripts.badcolumn_mask
×
59
import desispec.scripts.specex
×
60
import desispec.scripts.fiberflat
×
61
import desispec.scripts.humidity_corrected_fiberflat
×
62
import desispec.scripts.sky
×
63
import desispec.scripts.stdstars
×
64
import desispec.scripts.select_calib_stars
×
65
import desispec.scripts.fluxcalibration
×
66
import desispec.scripts.procexp
×
67
import desispec.scripts.nightly_bias
×
68
import desispec.scripts.fit_cte_night
×
69

70
from desispec.maskbits import ccdmask
×
71
from desispec.gpu import is_gpu_available
×
72

73
from desitarget.targetmask import desi_mask
×
74

75
from desiutil.log import get_logger, DEBUG, INFO
×
76
import desiutil.iers
×
77

78
from desispec.workflow.desi_proc_funcs import assign_mpi, get_desi_proc_parser, \
×
79
    update_args_with_headers, find_most_recent, log_timer
80
from desispec.workflow.desi_proc_funcs import determine_resources, create_desi_proc_batch_script
×
81

82
stop_imports = time.time()
×
83

84
#########################################
85
######## Begin Body of the Code #########
86
#########################################
87

88
def parse(options=None):
×
89
    parser = get_desi_proc_parser()
×
90
    args = parser.parse_args(options)
×
91
    return args
×
92

93
def main(args=None, comm=None):
×
94
    if not isinstance(args, argparse.Namespace):
×
95
        args = parse(args)
×
96

97
    log = get_logger()
×
98
    start_time = time.time()
×
99
    error_count = 0
×
100

101
    start_mpi_connect = time.time()
×
102
    if comm is not None:
×
103
        #- Use the provided comm to determine rank and size
104
        rank = comm.rank
×
105
        size = comm.size
×
106
    else:
107
        #- Check MPI flags and determine the comm, rank, and size given the arguments
108
        comm, rank, size = assign_mpi(do_mpi=args.mpi, do_batch=args.batch, log=log)
×
109
    stop_mpi_connect = time.time()
×
110

111
    if rank == 0:
×
112
        thisfile=os.path.dirname(os.path.abspath(__file__))
×
113
        thistime=datetime.datetime.fromtimestamp(start_imports).isoformat()
×
114
        log.info(f'rank 0 started {thisfile} at {thistime}')
×
115
    #- Start timer; only print log messages from rank 0 (others are silent)
116
    timer = desiutil.timer.Timer(silent=(rank>0))
×
117

118
    #- Fill in timing information for steps before we had the timer created
119
    if args.starttime is not None:
×
120
        timer.start('startup', starttime=args.starttime)
×
121
        timer.stop('startup', stoptime=start_imports)
×
122

123
    timer.start('imports', starttime=start_imports)
×
124
    timer.stop('imports', stoptime=stop_imports)
×
125

126
    timer.start('mpi_connect', starttime=start_mpi_connect)
×
127
    timer.stop('mpi_connect', stoptime=stop_mpi_connect)
×
128

129
    #- Use GPUs?
130
    if is_gpu_available():
×
131
        if args.no_gpu:
×
132
            log.warning("GPUs are available but not using them due to --no-gpu")
×
133
            use_gpu = False
×
134
        else:
135
            use_gpu = True
×
136
    else:
137
        use_gpu = False
×
138

139
    #- Freeze IERS after parsing args so that it doesn't bother if only --help
140
    timer.start('freeze_iers')
×
141
    desiutil.iers.freeze_iers()
×
142
    timer.stop('freeze_iers')
×
143

144
    #- Preflight checks
145
    timer.start('preflight')
×
146
    if rank > 0:
×
147
        #- Let rank 0 fetch these, and then broadcast
148
        args, hdr, camhdr = None, None, None
×
149
    else:
150
        if ( args.nightlybias or args.nightlycte ) and (args.expid is None) and (args.input is None):
×
151
            hdr = camhdr = None
×
152
        else:
153
            args, hdr, camhdr = update_args_with_headers(args)
×
154

155
    ## Make sure badamps is formatted properly
156
    if comm is not None and rank == 0 and args.badamps is not None:
×
157
        args.badamps = validate_badamps(args.badamps)
×
158

159
    if comm is not None:
×
160
        args = comm.bcast(args, root=0)
×
161
        hdr = comm.bcast(hdr, root=0)
×
162
        camhdr = comm.bcast(camhdr, root=0)
×
163

164
    if args.obstype is not None:
×
165
        args.obstype = args.obstype.upper()
×
166

167
    known_obstype = ['SCIENCE', 'ARC', 'FLAT', 'ZERO', 'DARK',
×
168
        'TESTARC', 'TESTFLAT', 'PIXFLAT', 'SKY', 'TWILIGHT', 'OTHER']
169
    only_nightlybias = args.nightlybias and args.expid is None
×
170
    if args.obstype not in known_obstype and (not only_nightlybias) and (not args.nightlycte) :
×
171
        raise ValueError('obstype {} not in {}'.format(args.obstype, known_obstype))
×
172

173
    if args.expid is None and (not args.nightlybias) and (not args.nightlycte) :
×
174
        msg = 'Must provide --expid or --nightlybias or --nightlycte'
×
175
        if rank == 0:
×
176
            log.critical(msg)
×
177

178
        sys.exit(1)
×
179

180
    if isinstance(args.cameras, str):
×
181
        args.cameras = decode_camword(args.cameras)
×
182

183
    if only_nightlybias  and args.cameras is None:
×
184
        args.cameras = decode_camword('a0123456789')
×
185
    if args.nightlycte and args.cameras is None:
×
186
        args.cameras = decode_camword('r0123456789z0123456789') # no CTE for blue
×
187

188
    timer.stop('preflight')
×
189

190
    #-------------------------------------------------------------------------
191
    #- Create and submit a batch job if requested
192

193
    if args.batch:
×
NEW
194
        if args.nightlycte and args.obstype != 'DARK' and not args.nightlybias:
×
NEW
195
            log.critical("don't know what to do in batch for just nightlycte!")
×
UNCOV
196
            sys.exit(1)
×
197

198
        #exp_str = '{:08d}'.format(args.expid)
199
        if args.obstype is not None:
×
200
            jobdesc = args.obstype.lower()
×
201
        elif only_nightlybias:
×
202
            jobdesc = 'nightlybias'
×
203
        else:
204
            log.critical('No --obstype, but also not just nightlybias ?!?')
×
205
            sys.exit(1)
×
206

207
        if args.obstype == 'DARK' and args.nightlybias:
×
208
            jobdesc = 'ccdcalib'
×
209

210
        if args.obstype == 'SCIENCE':
×
211
            # if not doing pre-stdstar fitting or stdstar fitting and if there is
212
            # no flag stopping flux calibration, set job to poststdstar
213
            if args.noprestdstarfit and args.nostdstarfit and (not args.nofluxcalib):
×
214
                jobdesc = 'poststdstar'
×
215
            # elif told not to do std or post stdstar but the flag for prestdstar isn't set,
216
            # then perform prestdstar
217
            elif (not args.noprestdstarfit) and args.nostdstarfit and args.nofluxcalib:
×
218
                jobdesc = 'prestdstar'
×
219
            #elif (not args.noprestdstarfit) and (not args.nostdstarfit) and (not args.nofluxcalib):
220
            #    jobdesc = 'science'
221
        scriptfile = create_desi_proc_batch_script(night=args.night, exp=args.expid,
×
222
                                                   cameras=args.cameras,
223
                                                   jobdesc=jobdesc, queue=args.queue,
224
                                                   runtime=args.runtime,
225
                                                   batch_opts=args.batch_opts,
226
                                                   timingfile=args.timingfile,
227
                                                   system_name=args.system_name,
228
                                                   nightlybias=args.nightlybias,
229
                                                   nightlycte=args.nightlycte,
230
                                                   cte_expids=args.cte_expids)
231
        err = 0
×
232
        if not args.nosubmit:
×
233
            err = subprocess.call(['sbatch', scriptfile])
×
234
        sys.exit(err)
×
235

236
    #-------------------------------------------------------------------------
237
    #- Proceeding with running
238

239
    #- What are we going to do?
240
    if rank == 0:
×
241
        log.info('----------')
×
242
        log.info('Input {}'.format(args.input))
×
243
        log.info('Night {} expid {}'.format(args.night, args.expid))
×
244
        log.info('Obstype {}'.format(args.obstype))
×
245
        log.info('Cameras {}'.format(args.cameras))
×
246
        log.info('Output root {}'.format(desispec.io.specprod_root()))
×
247
        log.info('----------')
×
248

249
    #-------------------------------------------------------------------------
250
    #- Create nightly bias from N>>1 ZEROs, but only for B-cameras
251
    if args.nightlybias:
×
252
        timer.start('nightlybias')
×
253

254
        camword = create_camword(args.cameras)
×
255
        cmd = f"desi_compute_nightly_bias -n {args.night} -c {camword}"
×
256

257
        # if args.bias_expids is not None:
258
        #     cmd += f" -e {args.bias_expids}"
259

260
        if rank == 0:
×
261
            log.info(f'RUNNING {cmd}')
×
262

263
        #- Note: nightly_bias may not produce all biasnight files if some
264
        #- are determined to be worse than the default, so check existence
265
        #- of output files separately.
266
        result, success = runcmd(desispec.scripts.nightly_bias.main,
×
267
                args=cmd.split()[1:], inputs=[], outputs=[], comm=comm)
268

269
        #- check for biasnight or biasnighttest output files
270
        missing_biasnight = 0
×
271
        if rank == 0:
×
272
            biasnightfiles = [findfile('biasnight', args.night, camera=cam) for cam in args.cameras]
×
273
            for filename in biasnightfiles:
×
274
                if not os.path.exists(filename):
×
275
                    #- ok for biasnight to be missing if biasnighttest is there
276
                    filename = replace_prefix(filename, 'biasnight', 'biasnighttest')
×
277
                    if not os.path.exists(filename):
×
278
                        missing_biasnight += 1
×
279

280
        if comm is not None:
×
281
            missing_biasnight = comm.bcast(missing_biasnight, root=0)
×
282

283
        success &= (missing_biasnight == 0)
×
284

285
        if not success:
×
286
            error_count += 1
×
287

288
        timer.stop('nightlybias')
×
289

290
    #-------------------------------------------------------------------------
291
    #- Create cte model from several LED exposures
292
    if args.nightlycte:
×
293

294
        timer.start('nightlycte')
×
295

296
        camword = create_camword(args.cameras)
×
297
        cmd = f"desi_fit_cte_night -n {args.night} -c {camword}"
×
298

NEW
299
        if args.cte_expids is not None:
×
NEW
300
            cmd += f" -e {args.cte_expids}"
×
301

UNCOV
302
        ctecorrnightfile = findfile('ctecorrnight', args.night)
×
303

304
        if rank == 0:
×
305
            log.info(f'RUNNING {cmd}')
×
306

307
        result, success = runcmd(desispec.scripts.fit_cte_night.main,
×
308
                args=cmd.split()[1:], inputs=[], outputs=[ctecorrnightfile,], comm=comm)
309

310
        if not success:
×
311
            error_count += 1
×
312

313
        timer.stop('nightlycte')
×
314

315
    #- this might be just nightly bias, or nightly cte, with no single exposure to process
316
    if args.expid is None:
×
317
        if comm is not None:
×
318
            all_error_counts = comm.gather(error_count, root=0)
×
319
            error_count = int(comm.bcast(np.sum(all_error_counts), root=0))
×
320

321
        if rank == 0:
×
322
            log.info('No expid given so stopping now')
×
323
            if error_count > 0:
×
324
                log.error(f'{error_count} processing errors; see logs above')
×
325

326
            duration_seconds = time.time() - start_time
×
327
            mm = int(duration_seconds) // 60
×
328
            ss = int(duration_seconds - mm*60)
×
329
            log.info('All done at {}; duration {}m{}s'.format(
×
330
                time.asctime(), mm, ss))
331

332
        sys.exit(error_count)
×
333

334

335
    #-------------------------------------------------------------------------
336
    #- Create output directories if needed
337
    if rank == 0:
×
338
        preprocdir = os.path.dirname(findfile('preproc', args.night, args.expid, 'b0'))
×
339
        expdir = os.path.dirname(findfile('frame', args.night, args.expid, 'b0'))
×
340
        os.makedirs(preprocdir, exist_ok=True)
×
341
        if args.obstype not in ('DARK', 'ZERO'):
×
342
            os.makedirs(expdir, exist_ok=True)
×
343

344
    #- Wait for rank 0 to make directories before proceeding
345
    if comm is not None:
×
346
        comm.barrier()
×
347

348
    #-------------------------------------------------------------------------
349
    #- Preproc
350
    #- All obstypes get preprocessed
351

352
    timer.start('fibermap')
×
353

354
    #- Assemble fibermap for science exposures
355
    fibermap = None
×
356
    fibermap_ok = None
×
357
    if rank == 0 and args.obstype == 'SCIENCE':
×
358
        fibermap = findfile('fibermap', args.night, args.expid)
×
359
        if not os.path.exists(fibermap):
×
360
            tmp = findfile('preproc', args.night, args.expid, 'b0')
×
361
            preprocdir = os.path.dirname(tmp)
×
362
            fibermap = os.path.join(preprocdir, os.path.basename(fibermap))
×
363

364
            tileid = hdr['TILEID']
×
365
            # tilepix = os.path.join(preprocdir, f'tilepix-{tileid}.json')
366
            tilepix = findfile('tilepix', args.night, args.expid, tile=tileid)
×
367

368
            log.info('Creating fibermap {}'.format(fibermap))
×
UNCOV
369
            cmd = 'assemble_fibermap -n {} -e {} -o {} -t {}'.format(
×
370
                    args.night, args.expid, fibermap, tilepix)
371
            if args.badamps is not None:
×
UNCOV
372
                cmd += ' --badamps={}'.format(args.badamps)
×
373
            cmdargs = cmd.split()[1:]
×
374
            result, success = runcmd(desispec.scripts.assemble_fibermap.main,
×
375
                    args=cmdargs, inputs=[], outputs=[fibermap, tilepix])
376

UNCOV
377
            if not success:
×
UNCOV
378
                error_count += 1
×
379

380
        fibermap_ok = os.path.exists(fibermap)
×
381

382
        #- Some commissioning files didn't have coords* files that caused assemble_fibermap to fail
383
        #- these are well known failures with no other solution, so for those, just force creation
384
        #- of a fibermap with null coordinate information
UNCOV
385
        if not fibermap_ok and int(args.night) < 20200310:
×
UNCOV
386
            log.info("Since night is before 20200310, trying to force fibermap creation without coords file")
×
387
            cmd += ' --force'
×
388
            cmdargs = cmd.split()[1:]
×
389
            result, success = runcmd(desispec.scripts.assemble_fibermap.main,
×
390
                    args=cmdargs, inputs=[], outputs=[fibermap])
391

UNCOV
392
            fibermap_ok = os.path.exists(fibermap)
×
UNCOV
393
            if not success or not fibermap_ok:
×
394
                error_count += 1
×
395

396
    #- If assemble_fibermap failed and obstype is SCIENCE, exit now
UNCOV
397
    if comm is not None:
×
UNCOV
398
        fibermap_ok = comm.bcast(fibermap_ok, root=0)
×
399

400
    if args.obstype == 'SCIENCE' and not fibermap_ok:
×
UNCOV
401
        sys.stdout.flush()
×
402
        if rank == 0:
×
403
            log.critical('assemble_fibermap failed for science exposure; exiting now')
×
404

405
        sys.exit(13)
×
406

407
    #- Wait for rank 0 to make fibermap if needed
UNCOV
408
    if comm is not None:
×
UNCOV
409
        fibermap = comm.bcast(fibermap, root=0)
×
410

411
    timer.stop('fibermap')
×
412

413
    if not (args.obstype in ['SCIENCE'] and args.noprestdstarfit):
×
UNCOV
414
        timer.start('preproc')
×
415
        for i in range(rank, len(args.cameras), size):
×
416
            camera = args.cameras[i]
×
417
            outfile = findfile('preproc', args.night, args.expid, camera)
×
418
            outdir = os.path.dirname(outfile)
×
419
            cmd = "desi_preproc -i {} -o {} --outdir {} --cameras {}".format(
×
420
                args.input, outfile, outdir, camera)
421
            if args.scattered_light :
×
UNCOV
422
                cmd += " --scattered-light"
×
423
            if args.obstype in ['SCIENCE'] and camera[0].lower() == "b" and ( not args.no_bkgsub ) :
×
424
                cmd += " --bkgsub-for-science"
×
425
            if fibermap is not None:
×
426
                cmd += " --fibermap {}".format(fibermap)
×
427
            if not args.obstype in ['ARC'] : # never model variance for arcs
×
428
                if not args.no_model_pixel_variance and args.obstype != 'DARK' :
×
429
                    cmd += " --model-variance"
×
430

431
            inputs = [args.input]
×
432

433
            #- TBD: require ctecorrnight file here, or allow to be missing?
434
            # if args.obstype not in ('ZERO', 'DARK') and camera[0].lower() != 'b':
435
            #     ctecorrfile = findfile('ctecorrnight', args.night, camera=camera)
436
            #     inputs.append(ctecorrfile)
437

UNCOV
438
            cmdargs = cmd.split()[1:]
×
UNCOV
439
            result, success = runcmd(desispec.scripts.preproc.main,
×
440
                    args=cmdargs, inputs=inputs, outputs=[outfile,])
441
            if not success:
×
UNCOV
442
                error_count += 1
×
443

444
        timer.stop('preproc')
×
UNCOV
445
        if comm is not None:
×
446
            comm.barrier()
×
447

448
    #-------------------------------------------------------------------------
449
    #- Get input PSFs
UNCOV
450
    timer.start('findpsf')
×
UNCOV
451
    input_psf = dict()
×
452
    if rank == 0 and args.obstype not in ['DARK',]:
×
453
        for camera in args.cameras :
×
454
            if args.psf is not None :
×
455
                input_psf[camera] = args.psf
×
456
            elif args.calibnight is not None :
×
457
                # look for a psfnight psf for this calib night
458
                psfnightfile = findfile('psfnight', args.calibnight, args.expid, camera, readonly=True)
×
UNCOV
459
                if not os.path.isfile(psfnightfile) :
×
460
                    log.error("no {}".format(psfnightfile))
×
461
                    raise IOError("no {}".format(psfnightfile))
×
462
                input_psf[camera] = psfnightfile
×
463
            else :
464
                # look for a psfnight psf
UNCOV
465
                psfnightfile = findfile('psfnight', args.night, args.expid, camera, readonly=True)
×
UNCOV
466
                if os.path.isfile(psfnightfile) :
×
467
                    input_psf[camera] = psfnightfile
×
468
                elif args.most_recent_calib:
×
469
                    nightfile = find_most_recent(args.night, file_type='psfnight')
×
470
                    if nightfile is None:
×
471
                        input_psf[camera] = findcalibfile([hdr, camhdr[camera]], 'PSF')
×
472
                    else:
473
                        input_psf[camera] = nightfile
×
474
                else :
475
                    input_psf[camera] = findcalibfile([hdr, camhdr[camera]], 'PSF')
×
476

477
            input_psf[camera] = get_readonly_filepath(input_psf[camera])
×
UNCOV
478
            log.info("Will use input PSF : {}".format(input_psf[camera]))
×
479

480
    if comm is not None:
×
UNCOV
481
        input_psf = comm.bcast(input_psf, root=0)
×
482

483
    timer.stop('findpsf')
×
484

485

486
    #-------------------------------------------------------------------------
487
    #- Dark (to detect bad columns)
488

UNCOV
489
    if args.obstype == 'DARK' :
×
490

491
        # check exposure time and perform a dark inspection only
492
        # if it is a 300s exposure
UNCOV
493
        exptime = None
×
UNCOV
494
        if rank == 0 :
×
495
            rawfilename=findfile('raw', args.night, args.expid, readonly=True)
×
496
            head=fitsio.read_header(rawfilename,1)
×
497
            exptime=head["EXPTIME"]
×
498
        if comm is not None :
×
499
            exptime = comm.bcast(exptime, root=0)
×
500

501
        if exptime > 270 and exptime < 330 :
×
UNCOV
502
            timer.start('inspect_dark')
×
503
            if rank == 0 :
×
504
                log.info('Starting desi_inspect_dark at {}'.format(time.asctime()))
×
505

506
            for i in range(rank, len(args.cameras), size):
×
UNCOV
507
                camera = args.cameras[i]
×
508
                preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
509
                badcolumnsfile = findfile('badcolumns', night=args.night, camera=camera)
×
510
                if not os.path.isfile(badcolumnsfile) :
×
511
                    cmd = "desi_inspect_dark"
×
512
                    cmd += " -i {}".format(preprocfile)
×
513
                    cmd += " --badcol-table {}".format(badcolumnsfile)
×
514
                    cmdargs = cmd.split()[1:]
×
515
                    result, success = runcmd(desispec.scripts.inspect_dark.main,
×
516
                            args=cmdargs, inputs=[preprocfile], outputs=[badcolumnsfile])
517

UNCOV
518
                    if not success:
×
UNCOV
519
                        error_count += 1
×
520
                else:
521
                    log.info(f'{badcolumnsfile} already exists; skipping desi_inspect_dark')
×
522

523
            if comm is not None :
×
UNCOV
524
                comm.barrier()
×
525

526
            timer.stop('inspect_dark')
×
UNCOV
527
        elif rank == 0:
×
528
            log.warning(f'Not running desi_inspect_dark for DARK with exptime={exptime:.1f}')
×
529

530
    #-------------------------------------------------------------------------
531
    #- Traceshift
532

UNCOV
533
    if ( args.obstype in ['FLAT', 'TESTFLAT', 'SKY', 'TWILIGHT']     )   or \
×
534
    ( args.obstype in ['SCIENCE'] and (not args.noprestdstarfit) ):
535

UNCOV
536
        timer.start('traceshift')
×
537

538
        if rank == 0 and args.traceshift :
×
UNCOV
539
            log.warning('desi_proc option --traceshift is deprecated because this is now the default')
×
540

541
        if rank == 0 and (not args.no_traceshift) :
×
UNCOV
542
            log.info('Starting traceshift at {}'.format(time.asctime()))
×
543

544
        for i in range(rank, len(args.cameras), size):
×
UNCOV
545
            camera = args.cameras[i]
×
546
            preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
547
            inpsf  = input_psf[camera]
×
548
            outpsf = findfile('psf', args.night, args.expid, camera)
×
549
            if not os.path.isfile(outpsf) :
×
550
                if (not args.no_traceshift):
×
551
                    cmd = "desi_compute_trace_shifts"
×
552
                    cmd += " -i {}".format(preprocfile)
×
553
                    cmd += " --psf {}".format(inpsf)
×
554
                    cmd += " --degxx 2 --degxy 0"
×
555
                    if args.obstype in ['FLAT', 'TESTFLAT', 'TWILIGHT'] :
×
556
                        cmd += " --continuum"
×
557
                    else :
558
                        cmd += " --degyx 2 --degyy 0"
×
UNCOV
559
                    if args.obstype in ['SCIENCE', 'SKY']:
×
560
                        cmd += ' --sky'
×
561
                    cmd += " --outpsf {}".format(outpsf)
×
562
                    cmdargs = cmd.split()[1:]
×
563
                    cmd = desispec.scripts.trace_shifts.main
×
564
                    expandargs = False
×
565
                else:
566
                    cmdargs = (inpsf, outpsf)
×
UNCOV
567
                    cmd = relsymlink
×
568
                    expandargs = True
×
569

570
                result, success = runcmd(cmd, args=cmdargs, expandargs=expandargs,
×
571
                        inputs=[preprocfile, inpsf], outputs=[outpsf])
572

UNCOV
573
                if not success:
×
UNCOV
574
                    error_count += 1
×
575
            else :
576
                log.info("PSF {} exists".format(outpsf))
×
577

578
        timer.stop('traceshift')
×
UNCOV
579
        if comm is not None:
×
580
            comm.barrier()
×
581

582
    #-------------------------------------------------------------------------
583
    #- PSF
584
    #- MPI parallelize this step
585

UNCOV
586
    if args.obstype in ['ARC', 'TESTARC']:
×
587

588
        timer.start('arc_traceshift')
×
589

590
        if rank == 0:
×
UNCOV
591
            log.info('Starting traceshift before specex PSF fit at {}'.format(time.asctime()))
×
592

593
        for i in range(rank, len(args.cameras), size):
×
UNCOV
594
            camera = args.cameras[i]
×
595
            preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
596
            inpsf  = input_psf[camera]
×
597
            outpsf = findfile('psf', args.night, args.expid, camera)
×
598
            outpsf = replace_prefix(outpsf, "psf", "shifted-input-psf")
×
599
            if not os.path.isfile(outpsf) :
×
600
                cmd = "desi_compute_trace_shifts"
×
601
                cmd += " -i {}".format(preprocfile)
×
602
                cmd += " --psf {}".format(inpsf)
×
603
                cmd += " --degxx 0 --degxy 0 --degyx 0 --degyy 0"
×
604
                cmd += ' --arc-lamps'
×
605
                cmd += " --outpsf {}".format(outpsf)
×
606
                cmdargs = cmd.split()[1:]
×
607
                result, success = runcmd(desispec.scripts.trace_shifts.main,
×
608
                        args=cmdargs, inputs=[preprocfile, inpsf], outputs=[outpsf])
609
                if not success:
×
UNCOV
610
                    error_count += 1
×
611

612
            else :
UNCOV
613
                log.info("PSF {} exists".format(outpsf))
×
614

615
        timer.stop('arc_traceshift')
×
UNCOV
616
        if comm is not None:
×
617
            comm.barrier()
×
618

619
        timer.start('psf')
×
620

621
        if rank == 0:
×
UNCOV
622
            log.info('Starting specex PSF fitting at {}'.format(time.asctime()))
×
623

624
        if rank > 0:
×
UNCOV
625
            cmds = inputs = outputs = None
×
626
        else:
627
            cmds = dict()
×
UNCOV
628
            inputs = dict()
×
629
            outputs = dict()
×
630
            for camera in args.cameras:
×
631
                preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
632
                tmpname = findfile('psf', args.night, args.expid, camera)
×
633
                inpsf = get_readonly_filepath(replace_prefix(tmpname,"psf","shifted-input-psf"))
×
634
                outpsf = replace_prefix(tmpname,"psf","fit-psf")
×
635

636
                log.info("now run specex psf fit")
×
637

638
                cmd = 'desi_compute_psf'
×
UNCOV
639
                cmd += ' --input-image {}'.format(preprocfile)
×
640
                cmd += ' --input-psf {}'.format(inpsf)
×
641
                cmd += ' --output-psf {}'.format(outpsf)
×
642

643
                # fibers to ignore for the PSF fit
644
                # specex uses the fiber index in a camera
UNCOV
645
                fibers_to_ignore = badfibers([hdr, camhdr[camera]],["BROKENFIBERS","BADCOLUMNFIBERS"])%500
×
UNCOV
646
                if fibers_to_ignore.size>0 :
×
647
                    fibers_to_ignore_str=str(fibers_to_ignore[0])
×
648
                    for fiber in fibers_to_ignore[1:] :
×
649
                        fibers_to_ignore_str+=",{}".format(fiber)
×
650
                    cmd += ' --broken-fibers {}'.format(fibers_to_ignore_str)
×
651
                    if rank == 0 :
×
652
                        log.warning('broken fibers: {}'.format(fibers_to_ignore_str))
×
653

654
                if not os.path.exists(outpsf):
×
UNCOV
655
                    cmds[camera] = cmd
×
656
                    inputs[camera] = [preprocfile, inpsf]
×
657
                    outputs[camera] = [outpsf,]
×
658

659
        if comm is not None:
×
UNCOV
660
            cmds = comm.bcast(cmds, root=0)
×
661
            if len(cmds) > 0:
×
662
                err = desispec.scripts.specex.run(comm,cmds,args.cameras)
×
663
                if err != 0:
×
664
                    error_count += 1
×
665
        else:
666
            log.warning('fitting PSFs without MPI parallelism; this will be SLOW')
×
UNCOV
667
            for camera in args.cameras:
×
668
                if camera in cmds:
×
669
                    result, success = runcmd(cmds[camera], inputs=inputs[camera], outputs=outputs[camera])
×
670
                    if not success:
×
671
                        error_count += 1
×
672

673
        timer.stop('psf')
×
UNCOV
674
        if comm is not None:
×
675
            comm.barrier()
×
676

677
        # loop on all cameras and interpolate bad fibers
UNCOV
678
        for camera in args.cameras[rank::size]:
×
UNCOV
679
            t0 = time.time()
×
680

681
            psfname = findfile('psf', args.night, args.expid, camera)
×
682
            #- NOTE: not readonly because we need to rename it later
683
            inpsf = replace_prefix(psfname,"psf","fit-psf")
×
684

685
            #- Check if a noisy amp might have corrupted this PSF;
686
            #- if so, rename to *.badreadnoise
687
            #- Currently the data is flagged per amp (25% of pixels), but do
688
            #- more generic test for 12.5% of pixels (half of one amp)
UNCOV
689
            log.info(f'Rank {rank} checking for noisy input CCD amps')
×
UNCOV
690
            preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
691
            mask = fitsio.read(preprocfile, 'MASK')
×
692
            noisyfrac = np.sum((mask & ccdmask.BADREADNOISE) != 0) / mask.size
×
693
            if noisyfrac > 0.25*0.5:
×
694
                log.error(f"{100*noisyfrac:.0f}% of {camera} input pixels have bad readnoise; don't use this PSF")
×
695
                if os.path.exists(inpsf):
×
696
                    os.rename(inpsf, inpsf+'.badreadnoise')
×
697
                continue
×
698

699
            log.info(f'Rank {rank} interpolating {camera} PSF over bad fibers')
×
700

701
            # fibers to ignore for the PSF fit
702
            # specex uses the fiber index in a camera
UNCOV
703
            fibers_to_ignore = badfibers([hdr, camhdr[camera]],["BROKENFIBERS","BADCOLUMNFIBERS"])%500
×
UNCOV
704
            if fibers_to_ignore.size>0 :
×
705
                fibers_to_ignore_str=str(fibers_to_ignore[0])
×
706
                for fiber in fibers_to_ignore[1:] :
×
707
                    fibers_to_ignore_str+=",{}".format(fiber)
×
708

709
                outpsf = replace_prefix(psfname,"psf","fit-psf-fixed-listed")
×
UNCOV
710
                if os.path.isfile(inpsf) and not os.path.isfile(outpsf):
×
711
                    cmd = 'desi_interpolate_fiber_psf'
×
712
                    cmd += ' --infile {}'.format(inpsf)
×
713
                    cmd += ' --outfile {}'.format(outpsf)
×
714
                    cmd += ' --fibers {}'.format(fibers_to_ignore_str)
×
715
                    log.info('For camera {} interpolating PSF for fibers: {}'.format(camera,fibers_to_ignore_str))
×
716
                    cmdargs = cmd.split()[1:]
×
717

718
                    result, success = runcmd(desispec.scripts.interpolate_fiber_psf.main,
×
719
                            args=cmdargs, inputs=[inpsf], outputs=[outpsf])
720

UNCOV
721
                    if not success:
×
UNCOV
722
                        error_count += 1
×
723

724
                    if os.path.isfile(outpsf) :
×
UNCOV
725
                        os.rename(inpsf,inpsf.replace("fit-psf","fit-psf-before-listed-fix"))
×
726
                        os.system('cp {} {}'.format(outpsf,inpsf))
×
727

728
            dt = time.time() - t0
×
UNCOV
729
            log.info(f'Rank {rank} {camera} PSF interpolation took {dt:.1f} sec')
×
730

731
    #-------------------------------------------------------------------------
732
    #- Extract
733
    #- This is MPI parallel so handle a bit differently
734

735
    # maybe add ARC and TESTARC too
UNCOV
736
    if ( args.obstype in ['FLAT', 'TESTFLAT', 'SKY', 'TWILIGHT']     )   or \
×
737
    ( args.obstype in ['SCIENCE'] and (not args.noprestdstarfit) ):
738

UNCOV
739
        timer.start('extract')
×
UNCOV
740
        if rank == 0:
×
741
            log.info('Starting extractions at {}'.format(time.asctime()))
×
742

743
        if rank > 0:
×
UNCOV
744
            cmds = inputs = outputs = None
×
745
        else:
746
            #- rank 0 collects commands to broadcast to others
UNCOV
747
            cmds = dict()
×
UNCOV
748
            inputs = dict()
×
749
            outputs = dict()
×
750
            for camera in args.cameras:
×
751
                cmd = 'desi_extract_spectra'
×
752

753
                #- Based on data from SM1-SM8, looking at central and edge fibers
754
                #- with in mind overlapping arc lamps lines
UNCOV
755
                if camera.startswith('b'):
×
UNCOV
756
                    cmd += ' -w 3600.0,5800.0,0.8'
×
757
                elif camera.startswith('r'):
×
758
                    cmd += ' -w 5760.0,7620.0,0.8'
×
759
                elif camera.startswith('z'):
×
760
                    cmd += ' -w 7520.0,9824.0,0.8'
×
761

762
                preprocfile = findfile('preproc', args.night, args.expid, camera, readonly=True)
×
UNCOV
763
                psffile = findfile('psf', args.night, args.expid, camera, readonly=True)
×
764
                finalframefile = findfile('frame', args.night, args.expid, camera)
×
765
                if os.path.exists(finalframefile):
×
766
                    log.info('{} already exists; not regenerating'.format(
×
767
                        os.path.basename(finalframefile)))
768
                    continue
×
769

770
                #- finalframefile doesn't exist; proceed with command
UNCOV
771
                framefile = finalframefile.replace(".fits","-no-badcolumn-mask.fits")
×
UNCOV
772
                cmd += ' -i {}'.format(preprocfile)
×
773
                cmd += ' -p {}'.format(psffile)
×
774
                cmd += ' -o {}'.format(framefile)
×
775

776
                #- Larger PSF model uncertainty for the blue cameras because a lower value
777
                #- results in many pixels with specmask.BAD2DFIT on the 5578A sky line.
UNCOV
778
                if camera.startswith('b'):
×
UNCOV
779
                    cmd += ' --psferr 0.04'
×
780
                else :
781
                    cmd += ' --psferr 0.01'
×
782

783
                if args.use_specter:
×
UNCOV
784
                    cmd += ' --use-specter'
×
785
                    cmd += ' --mpi'  # gpu_specter is MPI by default, but specter isn't
×
786

787
                if not use_gpu:
×
UNCOV
788
                    cmd += ' --no-gpu'
×
789

790
                if args.obstype == 'SCIENCE' or args.obstype == 'SKY' :
×
UNCOV
791
                    if not args.no_barycentric_correction :
×
792
                        log.info('Include barycentric correction')
×
793
                        cmd += ' --barycentric-correction'
×
794

795
                missing_inputs = False
×
UNCOV
796
                for infile in [preprocfile, psffile]:
×
797
                    if not os.path.exists(infile):
×
798
                        log.error(f'Missing {infile}')
×
799
                        missing_inputs = True
×
800

801
                if missing_inputs:
×
UNCOV
802
                    log.error(f'Camera {camera} missing inputs; skipping extractions')
×
803
                    continue
×
804

805
                if os.path.exists(framefile):
×
UNCOV
806
                    log.info(f'{framefile} already exists; skipping extraction')
×
807
                    continue
×
808

809
                cmds[camera] = cmd
×
UNCOV
810
                inputs[camera] = [preprocfile, psffile]
×
811
                outputs[camera] = [framefile,]
×
812

813
        #- TODO: refactor/combine this with PSF comm splitting logic
UNCOV
814
        if comm is not None:
×
UNCOV
815
            cmds = comm.bcast(cmds, root=0)
×
816
            inputs = comm.bcast(inputs, root=0)
×
817
            outputs = comm.bcast(outputs, root=0)
×
818

819
            if use_gpu and (not args.use_specter):
×
UNCOV
820
                import cupy as cp
×
821
                ngpus = cp.cuda.runtime.getDeviceCount()
×
822
                if rank == 0 and len(cmds)>0:
×
823
                    log.info(f"{rank} found {ngpus} gpus")
×
824

825
            #- Set extraction subcomm group size
UNCOV
826
            extract_subcomm_size = args.extract_subcomm_size
×
UNCOV
827
            if extract_subcomm_size is None:
×
828
                if args.use_specter:
×
829
                    #- CPU extraction with specter uses
830
                    #- 20 ranks.
UNCOV
831
                    extract_subcomm_size = 20
×
UNCOV
832
                elif use_gpu:
×
833
                    #- GPU extraction with gpu_specter uses
834
                    #- 5 ranks per GPU plus 2 for IO.
UNCOV
835
                    extract_subcomm_size = 2 + 5 * ngpus
×
836
                else:
837
                    #- CPU extraction with gpu_specter uses
838
                    #- 16 ranks.
UNCOV
839
                    extract_subcomm_size = 16
×
840

841
            #- Create list of ranks that will perform extraction
UNCOV
842
            if use_gpu:
×
843
                #- GPU extraction uses only one extraction group
844
                extract_group      = 0
×
UNCOV
845
                num_extract_groups = 1
×
846
            else:
847
                #- CPU extraction uses as many extraction groups as possible
UNCOV
848
                extract_group      = rank // extract_subcomm_size
×
UNCOV
849
                num_extract_groups = size // extract_subcomm_size
×
850
            extract_ranks = list(range(num_extract_groups*extract_subcomm_size))
×
851

852
            #- Create subcomm groups
UNCOV
853
            if use_gpu and len(cmds)>0:
×
UNCOV
854
                if rank in extract_ranks:
×
855
                    #- GPU extraction
856
                    extract_incl = comm.group.Incl(extract_ranks)
×
UNCOV
857
                    comm_extract = comm.Create_group(extract_incl)
×
858
                    from gpu_specter.mpi import ParallelIOCoordinator
×
859
                    coordinator = ParallelIOCoordinator(comm_extract)
×
860
            else:
861
                #- CPU extraction
UNCOV
862
                comm_extract = comm.Split(color=extract_group)
×
863

864
            if rank in extract_ranks and len(cmds)>0:
×
865
                #- Run the extractions
866
                for i in range(extract_group, len(args.cameras), num_extract_groups):
×
UNCOV
867
                    camera = args.cameras[i]
×
868
                    if camera in cmds:
×
869
                        cmdargs = cmds[camera].split()[1:]
×
870
                        extract_args = desispec.scripts.extract.parse(cmdargs)
×
871

872
                        if comm_extract.rank == 0:
×
UNCOV
873
                            print('RUNNING: {}'.format(cmds[camera]))
×
874

875
                        try:
×
UNCOV
876
                            if args.use_specter:
×
877
                                #- CPU extraction with specter
878
                                desispec.scripts.extract.main_mpi(extract_args, comm=comm_extract)
×
UNCOV
879
                            elif use_gpu:
×
880
                                #- GPU extraction with gpu_specter
881
                                desispec.scripts.extract.main_gpu_specter(extract_args, coordinator=coordinator)
×
882
                            else:
883
                                #- CPU extraction with gpu_specter
UNCOV
884
                                desispec.scripts.extract.main_gpu_specter(extract_args, comm=comm_extract)
×
UNCOV
885
                        except Exception as err:
×
886
                            import traceback
×
887
                            lines = traceback.format_exception(*sys.exc_info())
×
888
                            log.error(f"Camera {camera} extraction raised an exception:")
×
889
                            print("".join(lines))
×
890
                            error_count += 1
×
891

892
            elif len(cmds)>0:
×
893
                #- Skip this rank
894
                log.warning(f'rank {rank} idle during extraction step')
×
895

896
            comm.barrier()
×
897

898
        elif len(cmds)>0:
×
UNCOV
899
            log.warning('running extractions without MPI parallelism; this will be SLOW')
×
900
            for camera in args.cameras:
×
901
                if camera in cmds:
×
902
                    result, success = runcmd(cmds[camera], inputs=inputs[camera], outputs=outputs[camera])
×
903
                    if not success:
×
904
                        error_count += 1
×
905

906
        #- check for missing output files and log
UNCOV
907
        for camera in args.cameras:
×
UNCOV
908
            if camera in cmds:
×
909
                for outfile in outputs[camera]:
×
910
                    if not os.path.exists(outfile):
×
911
                        if comm is not None:
×
912
                            if comm.rank > 0:
×
913
                                continue
×
914
                        log.error(f'Camera {camera} extraction missing output {outfile}')
×
915
                        error_count += 1
×
916

917
        timer.stop('extract')
×
UNCOV
918
        if comm is not None:
×
919
            comm.barrier()
×
920

921
    #-------------------------------------------------------------------------
922
    #- Badcolumn specmask and fibermask
UNCOV
923
    if ( args.obstype in ['FLAT', 'TESTFLAT', 'SKY', 'TWILIGHT']     )   or \
×
924
       ( args.obstype in ['SCIENCE'] and (not args.noprestdstarfit) ):
925

UNCOV
926
        if rank==0 :
×
UNCOV
927
            log.info('Starting desi_compute_badcolumn_mask at {}'.format(time.asctime()))
×
928

929
        for i in range(rank, len(args.cameras), size):
×
UNCOV
930
            camera     = args.cameras[i]
×
931
            outfile    = findfile('frame', args.night, args.expid, camera)
×
932
            #- note: not readonly for "infile" since we'll remove it later
933
            infile     = outfile.replace(".fits","-no-badcolumn-mask.fits")
×
UNCOV
934
            psffile    = findfile('psf', args.night, args.expid, camera, readonly=True)
×
935
            badcolfile = findfile('badcolumns', night=args.night, camera=camera, readonly=True)
×
936
            cmd = "desi_compute_badcolumn_mask -i {} -o {} --psf {} --badcolumns {}".format(
×
937
                infile, outfile, psffile, badcolfile)
938

UNCOV
939
            if os.path.exists(outfile):
×
UNCOV
940
                log.info('{} already exists; not (re-)applying bad column mask'.format(os.path.basename(outfile)))
×
941
                continue
×
942

943
            if os.path.exists(badcolfile):
×
UNCOV
944
                cmdargs = cmd.split()[1:]
×
945

946
                result, success = runcmd(desispec.scripts.badcolumn_mask.main,
×
947
                        args=cmdargs, inputs=[infile,psffile,badcolfile], outputs=[outfile])
948

UNCOV
949
                if not success:
×
UNCOV
950
                    error_count += 1
×
951

952
                #- if successful, remove temporary frame-*-no-badcolumn-mask
UNCOV
953
                if os.path.isfile(outfile) :
×
UNCOV
954
                    log.info("rm "+infile)
×
955
                    os.unlink(infile)
×
956

957
            else:
UNCOV
958
                log.warning(f'Missing {badcolfile}; not applying badcol mask')
×
UNCOV
959
                log.info(f"mv {infile} {outfile}")
×
960
                os.rename(infile, outfile)
×
961

962
        if comm is not None :
×
UNCOV
963
            comm.barrier()
×
964

965
    #-------------------------------------------------------------------------
966
    #- Fiberflat
967

UNCOV
968
    if args.obstype in ['FLAT', 'TESTFLAT'] :
×
UNCOV
969
        exptime = None
×
970
        if rank == 0 :
×
971
            rawfilename=findfile('raw', args.night, args.expid, readonly=True)
×
972
            head=fitsio.read_header(rawfilename,1)
×
973
            exptime=head["EXPTIME"]
×
974
        if comm is not None :
×
975
            exptime = comm.bcast(exptime, root=0)
×
976

977
        if exptime > 10:
×
UNCOV
978
            timer.start('fiberflat')
×
979
            if rank == 0:
×
980
                log.info('Flat exposure time was greater than 10 seconds')
×
981
                log.info('Starting fiberflats at {}'.format(time.asctime()))
×
982

983
            for i in range(rank, len(args.cameras), size):
×
UNCOV
984
                camera = args.cameras[i]
×
985
                framefile = findfile('frame', args.night, args.expid, camera, readonly=True)
×
986
                fiberflatfile = findfile('fiberflat', args.night, args.expid, camera)
×
987
                cmd = "desi_compute_fiberflat"
×
988
                cmd += " -i {}".format(framefile)
×
989
                cmd += " -o {}".format(fiberflatfile)
×
990
                cmdargs = cmd.split()[1:]
×
991

992
                result, success = runcmd(desispec.scripts.fiberflat.main,
×
993
                        args=cmdargs, inputs=[framefile,], outputs=[fiberflatfile,])
994

UNCOV
995
                if not success:
×
UNCOV
996
                    error_count += 1
×
997

998
            timer.stop('fiberflat')
×
UNCOV
999
            if comm is not None:
×
1000
                comm.barrier()
×
1001

1002
    #-------------------------------------------------------------------------
1003
    #- Get input fiberflat
UNCOV
1004
    if args.obstype in ['SCIENCE', 'SKY'] and (not args.nofiberflat):
×
UNCOV
1005
        timer.start('find_fiberflat')
×
1006
        input_fiberflat = dict()
×
1007
        if rank == 0:
×
1008
            for camera in args.cameras :
×
1009
                if args.fiberflat is not None :
×
1010
                    input_fiberflat[camera] = args.fiberflat
×
1011
                elif args.calibnight is not None :
×
1012
                    # look for a fiberflatnight for this calib night
1013
                    fiberflatnightfile = findfile('fiberflatnight',
×
1014
                            args.calibnight, args.expid, camera)
1015
                    if not os.path.isfile(fiberflatnightfile) :
×
UNCOV
1016
                        log.error("no {}".format(fiberflatnightfile))
×
1017
                        raise IOError("no {}".format(fiberflatnightfile))
×
1018
                    input_fiberflat[camera] = fiberflatnightfile
×
1019
                else :
1020
                    # look for a fiberflatnight fiberflat
UNCOV
1021
                    fiberflatnightfile = findfile('fiberflatnight',
×
1022
                            args.night, args.expid, camera)
1023
                    if os.path.isfile(fiberflatnightfile) :
×
UNCOV
1024
                        input_fiberflat[camera] = fiberflatnightfile
×
1025
                    elif args.most_recent_calib:
×
1026
                        nightfile = find_most_recent(args.night, file_type='fiberflatnight')
×
1027
                        if nightfile is None:
×
1028
                            input_fiberflat[camera] = findcalibfile([hdr, camhdr[camera]], 'FIBERFLAT')
×
1029
                        else:
1030
                            input_fiberflat[camera] = nightfile
×
1031
                    else :
1032
                        input_fiberflat[camera] = findcalibfile(
×
1033
                                [hdr, camhdr[camera]], 'FIBERFLAT')
1034

UNCOV
1035
                input_fiberflat[camera] = get_readonly_filepath(input_fiberflat[camera])
×
UNCOV
1036
                log.info("Will use input FIBERFLAT: {}".format(input_fiberflat[camera]))
×
1037

1038
        if comm is not None:
×
UNCOV
1039
            input_fiberflat = comm.bcast(input_fiberflat, root=0)
×
1040

1041
        timer.stop('find_fiberflat')
×
1042

1043
    #-------------------------------------------------------------------------
1044
    #- Fiber flat corrected for humidity
UNCOV
1045
    if args.obstype in ['SCIENCE', 'SKY'] and (not args.noprestdstarfit):
×
1046

1047
        timer.start('fiberflat_humidity_correction')
×
1048

1049
        if rank == 0:
×
UNCOV
1050
            log.info('Flatfield correction for humidity {}'.format(time.asctime()))
×
1051

1052
        for i in range(rank, len(args.cameras), size):
×
UNCOV
1053
            camera = args.cameras[i]
×
1054
            framefile = findfile('frame', args.night, args.expid, camera, readonly=True)
×
1055
            input_fiberflatfile=input_fiberflat[camera]
×
1056
            if input_fiberflatfile is None :
×
1057
                log.error("No input fiberflat for {}".format(camera))
×
1058
                continue
×
1059

1060
            # First need a flatfield per exposure
UNCOV
1061
            fiberflatfile = findfile('fiberflatexp', args.night, args.expid, camera)
×
1062

1063
            cmd = "desi_compute_humidity_corrected_fiberflat"
×
UNCOV
1064
            cmd += " --use-sky-fibers"
×
1065
            cmd += " -i {}".format(framefile)
×
1066
            cmd += " --fiberflat {}".format(input_fiberflatfile)
×
1067
            cmd += " -o {}".format(fiberflatfile)
×
1068
            cmdargs = cmd.split()[1:]
×
1069

1070
            result, success = runcmd(desispec.scripts.humidity_corrected_fiberflat.main,
×
1071
                    args=cmdargs, inputs=[framefile, input_fiberflatfile], outputs=[fiberflatfile,])
1072

UNCOV
1073
            if not success:
×
UNCOV
1074
                error_count += 1
×
1075

1076
        timer.stop('fiberflat_humidity_correction')
×
UNCOV
1077
        if comm is not None:
×
1078
            comm.barrier()
×
1079

1080
    #-------------------------------------------------------------------------
1081
    #- Apply fiberflat and write fframe file
1082

UNCOV
1083
    if args.obstype in ['SCIENCE', 'SKY'] and args.fframe and \
×
1084
    ( not args.nofiberflat ) and (not args.noprestdstarfit):
1085
        timer.start('apply_fiberflat')
×
UNCOV
1086
        if rank == 0:
×
1087
            log.info('Applying fiberflat at {}'.format(time.asctime()))
×
1088

1089
        for i in range(rank, len(args.cameras), size):
×
UNCOV
1090
            camera = args.cameras[i]
×
1091
            fframefile = findfile('fframe', args.night, args.expid, camera)
×
1092
            if not os.path.exists(fframefile):
×
1093
                framefile = findfile('frame', args.night, args.expid, camera, readonly=True)
×
1094
                fr = desispec.io.read_frame(framefile)
×
1095
                flatfilename = findfile('fiberflatexp', args.night, args.expid, camera, readonly=True)
×
1096
                ff = desispec.io.read_fiberflat(flatfilename)
×
1097
                fr.meta['FIBERFLT'] = desispec.io.shorten_filename(flatfilename)
×
1098
                apply_fiberflat(fr, ff)
×
1099
                fframefile = findfile('fframe', args.night, args.expid, camera)
×
1100
                desispec.io.write_frame(fframefile, fr)
×
1101

1102
        timer.stop('apply_fiberflat')
×
UNCOV
1103
        if comm is not None:
×
1104
            comm.barrier()
×
1105

1106
    #-------------------------------------------------------------------------
1107
    #- Select random sky fibers (inplace update of frame file)
1108
    #- TODO: move this to a function somewhere
1109
    #- TODO: this assigns different sky fibers to each frame of same spectrograph
1110

UNCOV
1111
    if (args.obstype in ['SKY', 'SCIENCE']) and (not args.noskysub) and (not args.noprestdstarfit):
×
UNCOV
1112
        timer.start('picksky')
×
1113
        if rank == 0:
×
1114
            log.info('Picking sky fibers at {}'.format(time.asctime()))
×
1115

1116
        for i in range(rank, len(args.cameras), size):
×
UNCOV
1117
            camera = args.cameras[i]
×
1118
            framefile = findfile('frame', args.night, args.expid, camera, readonly=True)
×
1119
            orig_frame = desispec.io.read_frame(framefile)
×
1120

1121
            #- Make a copy so that we can apply fiberflat
UNCOV
1122
            fr = deepcopy(orig_frame)
×
1123

1124
            if np.any(fr.fibermap['OBJTYPE'] == 'SKY'):
×
UNCOV
1125
                log.info('{} sky fibers already set; skipping'.format(
×
1126
                    os.path.basename(framefile)))
1127
                continue
×
1128

1129
            #- Apply fiberflat then select random fibers below a flux cut
UNCOV
1130
            flatfilename = findfile('fiberflatexp', args.night, args.expid, camera, readonly=True)
×
UNCOV
1131
            ff = desispec.io.read_fiberflat(flatfilename)
×
1132
            apply_fiberflat(fr, ff)
×
1133
            sumflux = np.sum(fr.flux, axis=1)
×
1134
            fluxcut = np.percentile(sumflux, 30)
×
1135
            iisky = np.where(sumflux < fluxcut)[0]
×
1136
            iisky = np.random.choice(iisky, size=100, replace=False)
×
1137

1138
            #- Update fibermap or original frame and write out
UNCOV
1139
            orig_frame.fibermap['OBJTYPE'][iisky] = 'SKY'
×
UNCOV
1140
            orig_frame.fibermap['DESI_TARGET'][iisky] |= desi_mask.SKY
×
1141

1142
            desispec.io.write_frame(framefile, orig_frame)
×
1143

1144
        timer.stop('picksky')
×
UNCOV
1145
        if comm is not None:
×
1146
            comm.barrier()
×
1147

1148
    #-------------------------------------------------------------------------
1149
    #- Sky subtraction
UNCOV
1150
    if args.obstype in ['SCIENCE', 'SKY'] and (not args.noskysub ) and (not args.noprestdstarfit):
×
1151

1152
        timer.start('skysub')
×
UNCOV
1153
        if rank == 0:
×
1154
            log.info('Starting sky subtraction at {}'.format(time.asctime()))
×
1155

1156
        for i in range(rank, len(args.cameras), size):
×
UNCOV
1157
            camera = args.cameras[i]
×
1158
            framefile = findfile('frame', args.night, args.expid, camera, readonly=True)
×
1159
            hdr = fitsio.read_header(framefile, 'FLUX')
×
1160
            fiberflatfile = findfile('fiberflatexp', args.night, args.expid, camera, readonly=True)
×
1161
            skyfile = findfile('sky', args.night, args.expid, camera)
×
1162

1163
            cmd = "desi_compute_sky"
×
UNCOV
1164
            cmd += " -i {}".format(framefile)
×
1165
            cmd += " --fiberflat {}".format(fiberflatfile)
×
1166
            cmd += " -o {}".format(skyfile)
×
1167
            if args.no_extra_variance :
×
1168
                cmd += " --no-extra-variance"
×
1169
            if not args.no_sky_wavelength_adjustment : cmd += " --adjust-wavelength"
×
1170
            if not args.no_sky_lsf_adjustment : cmd += " --adjust-lsf"
×
1171
            if (not args.no_sky_wavelength_adjustment) and (not args.no_sky_lsf_adjustment) and args.save_sky_adjustments :
×
1172
                cmd += " --save-adjustments {}".format(skyfile.replace("sky-","skycorr-"))
×
1173
            if args.adjust_sky_with_more_fibers :
×
1174
                cmd += " --adjust-with-more-fibers"
×
1175
            if (not args.no_sky_wavelength_adjustment) or (not args.no_sky_lsf_adjustment) :
×
1176
                pca_corr_filename = findcalibfile([hdr, camhdr[camera]], 'SKYCORR')
×
1177
                if pca_corr_filename is not None :
×
1178
                    cmd += " --pca-corr {}".format(pca_corr_filename)
×
1179
                else :
1180
                    log.warning("No SKYCORR file, do you need to update DESI_SPECTRO_CALIB?")
×
UNCOV
1181
            cmd += " --fit-offsets"
×
1182
            if not args.no_skygradpca:
×
1183
                skygradpca_filename = findcalibfile([hdr, camhdr[camera]], 'SKYGRADPCA')
×
1184
                if skygradpca_filename is not None :
×
1185
                    cmd += " --skygradpca {}".format(skygradpca_filename)
×
1186
                else :
1187
                    log.warning("No SKYGRADPCA file, do you need to update DESI_SPECTRO_CALIB?")
×
1188

1189
            if not args.no_tpcorrparam:
×
UNCOV
1190
                tpcorrparam_filename = findcalibfile([hdr, camhdr[camera]], 'TPCORRPARAM')
×
1191
                if tpcorrparam_filename is not None :
×
1192
                    cmd += " --tpcorrparam {}".format(tpcorrparam_filename)
×
1193
                else :
1194
                    log.warning("No TPCORRPARAM file, do you need to update DESI_SPECTRO_CALIB?")
×
UNCOV
1195
            cmdargs = cmd.split()[1:]
×
1196

1197
            result, success = runcmd(desispec.scripts.sky.main,
×
1198
                    args=cmdargs, inputs=[framefile, fiberflatfile], outputs=[skyfile,])
1199

UNCOV
1200
            if not success:
×
UNCOV
1201
                error_count += 1
×
1202

1203
            #- sframe = flatfielded sky-subtracted but not flux calibrated frame
1204
            #- Note: this re-reads and re-does steps previously done for picking
1205
            #- sky fibers; desi_proc is about human efficiency,
1206
            #- not I/O or CPU efficiency...
UNCOV
1207
            sframefile = desispec.io.findfile('sframe', args.night, args.expid, camera)
×
UNCOV
1208
            if not os.path.exists(sframefile):
×
1209
                missing_inputs = False
×
1210
                for filename in [framefile, fiberflatfile, skyfile]:
×
1211
                    if not os.path.exists(filename):
×
1212
                        log.error(f'Camera {camera} missing sframe input {filename}')
×
1213
                        missing_inputs = True
×
1214

1215
                if missing_inputs:
×
UNCOV
1216
                    log.error(f'Camera {camera} missing sframe inputs; skipping')
×
1217
                    error_count += 1
×
1218
                else:
1219
                    try:
×
UNCOV
1220
                        frame = desispec.io.read_frame(framefile)
×
1221
                        fiberflat = desispec.io.read_fiberflat(fiberflatfile)
×
1222
                        sky = desispec.io.read_sky(skyfile)
×
1223
                        apply_fiberflat(frame, fiberflat)
×
1224
                        subtract_sky(frame, sky, apply_throughput_correction=(
×
1225
                            args.apply_sky_throughput_correction))
1226
                        frame.meta['IN_SKY'] = shorten_filename(skyfile)
×
UNCOV
1227
                        frame.meta['FIBERFLT'] = shorten_filename(fiberflatfile)
×
1228
                        desispec.io.write_frame(sframefile, frame)
×
1229
                    except Exception as err:
×
1230
                        import traceback
×
1231
                        lines = traceback.format_exception(*sys.exc_info())
×
1232
                        log.error(f"Camera {camera} sframe raised an exception:")
×
1233
                        print("".join(lines))
×
1234
                        log.warning(f'Continuing without {sframefile}')
×
1235
                        error_count += 1
×
1236

1237
        timer.stop('skysub')
×
UNCOV
1238
        if comm is not None:
×
1239
            comm.barrier()
×
1240

1241
    #-------------------------------------------------------------------------
1242
    #- Standard Star Fitting
1243

UNCOV
1244
    if args.obstype in ['SCIENCE',] and \
×
1245
            (not args.noskysub ) and \
1246
            (not args.nostdstarfit) :
1247

UNCOV
1248
        timer.start('stdstarfit')
×
UNCOV
1249
        if rank == 0:
×
1250
            log.info('Starting flux calibration at {}'.format(time.asctime()))
×
1251

1252
        #- Group inputs by spectrograph
UNCOV
1253
        framefiles = dict()
×
UNCOV
1254
        skyfiles = dict()
×
1255
        fiberflatfiles = dict()
×
1256
        night, expid = args.night, args.expid #- shorter
×
1257
        for camera in args.cameras:
×
1258
            sp = int(camera[1])
×
1259
            if sp not in framefiles:
×
1260
                framefiles[sp] = list()
×
1261
                skyfiles[sp] = list()
×
1262
                fiberflatfiles[sp] = list()
×
1263

1264
            framefiles[sp].append(findfile('frame', night, expid, camera, readonly=True))
×
UNCOV
1265
            skyfiles[sp].append(findfile('sky', night, expid, camera, readonly=True))
×
1266
            fiberflatfiles[sp].append(findfile('fiberflatexp', night, expid, camera, readonly=True))
×
1267

1268
        #- Hardcoded stdstar model version
UNCOV
1269
        starmodels = os.path.join(
×
1270
            os.getenv('DESI_BASIS_TEMPLATES'), 'stdstar_templates_v2.2.fits')
1271

1272
        #- Fit stdstars per spectrograph (not per-camera)
UNCOV
1273
        spectro_nums = sorted(framefiles.keys())
×
1274

1275
        if args.mpistdstars and comm is not None:
×
1276
            #- If using MPI parallelism in stdstar fit, divide comm into subcommunicators.
1277
            #- (spectro_start, spectro_step) determine stride pattern over spectro_nums.
1278
            #- Split comm by at most len(spectro_nums)
UNCOV
1279
            num_subcomms = min(size, len(spectro_nums))
×
UNCOV
1280
            subcomm_index = rank % num_subcomms
×
1281
            if rank == 0:
×
1282
                log.info(f"Splitting comm of {size=} into {num_subcomms=} for stdstar fitting")
×
1283
            subcomm = comm.Split(color=subcomm_index)
×
1284
            spectro_start, spectro_step = subcomm_index, num_subcomms
×
1285
        else:
1286
            #- Otherwise, use multiprocessing assuming 1 MPI rank per spectrograph
UNCOV
1287
            spectro_start, spectro_step = rank, size
×
UNCOV
1288
            subcomm = None
×
1289

1290
        for i in range(spectro_start, len(spectro_nums), spectro_step):
×
UNCOV
1291
            sp = spectro_nums[i]
×
1292

1293
            stdfile = findfile('stdstars', night, expid, spectrograph=sp)
×
UNCOV
1294
            cmd = "desi_fit_stdstars"
×
1295
            cmd += " --frames {}".format(' '.join(framefiles[sp]))
×
1296
            cmd += " --skymodels {}".format(' '.join(skyfiles[sp]))
×
1297
            cmd += " --fiberflats {}".format(' '.join(fiberflatfiles[sp]))
×
1298
            cmd += " --starmodels {}".format(starmodels)
×
1299
            cmd += " --outfile {}".format(stdfile)
×
1300
            cmd += " --delta-color 0.1"
×
1301
            if args.maxstdstars is not None:
×
1302
                cmd += " --maxstdstars {}".format(args.maxstdstars)
×
1303
            if args.apply_sky_throughput_correction :
×
1304
                cmd += " --apply-sky-throughput-correction"
×
1305
            inputs = framefiles[sp] + skyfiles[sp] + fiberflatfiles[sp]
×
1306
            err = 0
×
1307
            cmdargs = cmd.split()[1:]
×
1308

1309
            if subcomm is None:
×
1310
                #- Using multiprocessing
1311
                log.info(f'Rank {rank=} fitting sp{sp=} stdstars with multiprocessing')
×
UNCOV
1312
                result, success = runcmd(desispec.scripts.stdstars.main,
×
1313
                    args=cmdargs, inputs=inputs, outputs=[stdfile])
1314
            else:
1315
                #- Using MPI
UNCOV
1316
                log.info(f'Rank {rank=} fitting sp{sp=} stdstars with mpi')
×
UNCOV
1317
                result, success = runcmd(desispec.scripts.stdstars.main,
×
1318
                    args=cmdargs, inputs=inputs, outputs=[stdfile], comm=subcomm)
1319

UNCOV
1320
            if not success:
×
UNCOV
1321
                log.info(f'Rank {rank=} stdstar failure {err=}')
×
1322
                error_count += 1
×
1323

1324
        timer.stop('stdstarfit')
×
UNCOV
1325
        if comm is not None:
×
1326
            comm.barrier()
×
1327

1328
    # -------------------------------------------------------------------------
1329
    # - Flux calibration
1330

UNCOV
1331
    def list2str(xx) :
×
1332
        """converts list xx to string even if elements aren't strings"""
1333
        return " ".join([str(x) for x in xx])
×
1334

1335
    if args.obstype in ['SCIENCE'] and \
×
1336
                (not args.noskysub) and \
1337
                (not args.nofluxcalib):
UNCOV
1338
        timer.start('fluxcalib')
×
1339

1340
        night, expid = args.night, args.expid #- shorter
×
1341

1342
        if rank == 0 :
×
UNCOV
1343
            r_cameras = []
×
1344
            for camera in args.cameras :
×
1345
                if camera[0] == 'r' :
×
1346
                    r_cameras.append(camera)
×
1347
            if len(r_cameras)>0 :
×
1348
                outfile    = findfile('calibstars',night, expid)
×
1349
                frames     = [findfile('frame', night, expid, camera, readonly=True) for camera in r_cameras]
×
1350
                fiberflats = [findfile('fiberflatexp', night, expid, camera, readonly=True) for camera in r_cameras]
×
1351
                skys       = [findfile('sky', night, expid, camera, readonly=True) for camera in r_cameras]
×
1352
                models     = [findfile('stdstars', night, expid,spectrograph=int(camera[1]), readonly=True) for camera in r_cameras]
×
1353

1354
                inputs = frames + fiberflats + skys + models
×
UNCOV
1355
                cmd = "desi_select_calib_stars --delta-color-cut 0.1 "
×
1356
                cmd += " --frames {}".format(list2str(frames))
×
1357
                cmd += " --fiberflats {}".format(list2str(fiberflats))
×
1358
                cmd += " --skys {}".format(list2str(skys))
×
1359
                cmd += " --models {}".format(list2str(models))
×
1360
                cmd += f" -o {outfile}"
×
1361
                cmdargs = cmd.split()[1:]
×
1362
                result, success = runcmd(desispec.scripts.select_calib_stars.main,
×
1363
                        args=cmdargs, inputs=inputs, outputs=[outfile,])
1364

UNCOV
1365
                if not success:
×
UNCOV
1366
                    error_count += 1
×
1367

1368
        if comm is not None:
×
UNCOV
1369
            comm.barrier()
×
1370

1371
        #- Compute flux calibration vectors per camera
UNCOV
1372
        for camera in args.cameras[rank::size]:
×
UNCOV
1373
            framefile = findfile('frame', night, expid, camera, readonly=True)
×
1374
            skyfile = findfile('sky', night, expid, camera, readonly=True)
×
1375
            spectrograph = int(camera[1])
×
1376
            stdfile = findfile('stdstars', night, expid,spectrograph=spectrograph, readonly=True)
×
1377
            fiberflatfile = findfile('fiberflatexp', night, expid, camera, readonly=True)
×
1378
            calibfile = findfile('fluxcalib', night, expid, camera)
×
1379
            calibstars = findfile('calibstars',night, expid)
×
1380

1381
            cmd = "desi_compute_fluxcalibration"
×
UNCOV
1382
            cmd += " --infile {}".format(framefile)
×
1383
            cmd += " --sky {}".format(skyfile)
×
1384
            cmd += " --fiberflat {}".format(fiberflatfile)
×
1385
            cmd += " --models {}".format(stdfile)
×
1386
            cmd += " --outfile {}".format(calibfile)
×
1387
            cmd += " --selected-calibration-stars {}".format(calibstars)
×
1388
            if args.apply_sky_throughput_correction :
×
1389
                cmd += " --apply-sky-throughput-correction"
×
1390

1391
            inputs = [framefile, skyfile, fiberflatfile, stdfile, calibstars]
×
UNCOV
1392
            cmdargs = cmd.split()[1:]
×
1393

1394
            result, success = runcmd(desispec.scripts.fluxcalibration.main,
×
1395
                    args=cmdargs, inputs=inputs, outputs=[calibfile,])
1396

UNCOV
1397
            if not success:
×
UNCOV
1398
                error_count += 1
×
1399

1400
        timer.stop('fluxcalib')
×
UNCOV
1401
        if comm is not None:
×
1402
            comm.barrier()
×
1403

1404
    #-------------------------------------------------------------------------
1405
    #- Applying flux calibration
1406

UNCOV
1407
    if args.obstype in ['SCIENCE',] and (not args.noskysub ) and (not args.nofluxcalib) :
×
1408

1409
        night, expid = args.night, args.expid #- shorter
×
1410

1411
        timer.start('applycalib')
×
UNCOV
1412
        if rank == 0:
×
1413
            log.info('Starting cframe file creation at {}'.format(time.asctime()))
×
1414

1415
        for camera in args.cameras[rank::size]:
×
UNCOV
1416
            framefile = findfile('frame', night, expid, camera, readonly=True)
×
1417
            fiberflatfile = findfile('fiberflatexp', night, expid, camera, readonly=True)
×
1418
            skyfile = findfile('sky', night, expid, camera, readonly=True)
×
1419
            spectrograph = int(camera[1])
×
1420
            stdfile = findfile('stdstars', night, expid, spectrograph=spectrograph, readonly=True)
×
1421
            calibfile = findfile('fluxcalib', night, expid, camera, readonly=True)
×
1422
            cframefile = findfile('cframe', night, expid, camera)
×
1423

1424
            cmd = "desi_process_exposure"
×
UNCOV
1425
            cmd += " --infile {}".format(framefile)
×
1426
            cmd += " --fiberflat {}".format(fiberflatfile)
×
1427
            cmd += " --sky {}".format(skyfile)
×
1428
            cmd += " --calib {}".format(calibfile)
×
1429
            cmd += " --outfile {}".format(cframefile)
×
1430
            if args.apply_sky_throughput_correction :
×
1431
                cmd += " --apply-sky-throughput-correction"
×
1432
            cmd += " --cosmics-nsig 6"
×
1433
            if args.no_xtalk :
×
1434
                cmd += " --no-xtalk"
×
1435

1436
            inputs = [framefile, fiberflatfile, skyfile, calibfile]
×
UNCOV
1437
            cmdargs = cmd.split()[1:]
×
1438

1439
            result, success = runcmd(desispec.scripts.procexp.main, args=cmdargs, inputs=inputs, outputs=[cframefile,])
×
1440

1441
            if not success:
×
UNCOV
1442
                error_count += 1
×
1443

1444
        if comm is not None:
×
UNCOV
1445
            comm.barrier()
×
1446

1447
        timer.stop('applycalib')
×
1448

1449
    #-------------------------------------------------------------------------
1450
    #- Exposure QA, using same criterion as fluxcalib for when to run
1451

UNCOV
1452
    if args.obstype in ['SCIENCE',] and (not args.noskysub ) and (not args.nofluxcalib) :
×
UNCOV
1453
        from desispec.scripts import exposure_qa
×
1454

1455
        night, expid = args.night, args.expid #- shorter
×
1456

1457
        timer.start('exposure_qa')
×
UNCOV
1458
        if rank == 0:
×
1459
            log.info('Starting exposure_qa at {}'.format(time.asctime()))
×
1460

1461
        #- exposure QA not yet parallelized for a single exposure
UNCOV
1462
        if rank == 0:
×
UNCOV
1463
            qa_args = ['-n', str(night), '-e', str(expid), '--nproc', str(1)]
×
1464
            try:
×
1465
                exposure_qa.main(exposure_qa.parse(qa_args))
×
1466
            except Exception as err:
×
1467
                #- log exceptions, but don't treat QA problems as fatal
1468
                import traceback
×
UNCOV
1469
                lines = traceback.format_exception(*sys.exc_info())
×
1470
                log.error(f"exposure_qa raised an exception:")
×
1471
                print("".join(lines))
×
1472
                log.warning(f"QA exception not treated as blocking failure")
×
1473

1474
        #- Make other ranks wait anyway
UNCOV
1475
        if comm is not None:
×
UNCOV
1476
            comm.barrier()
×
1477

1478
        timer.stop('exposure_qa')
×
1479

1480
    #-------------------------------------------------------------------------
1481
    #- Collect error count and wrap up
UNCOV
1482
    if comm is not None:
×
UNCOV
1483
        all_error_counts = comm.gather(error_count, root=0)
×
1484
        error_count = int(comm.bcast(np.sum(all_error_counts), root=0))
×
1485

1486
    #- save / print timing information
UNCOV
1487
    log_timer(timer, args.timingfile, comm=comm)
×
1488

1489
    if rank == 0:
×
UNCOV
1490
        duration_seconds = time.time() - start_time
×
1491
        mm = int(duration_seconds) // 60
×
1492
        ss = int(duration_seconds - mm*60)
×
1493
        goodbye = f'All done at {time.asctime()}; duration {mm}m{ss}s'
×
1494

1495
        if error_count > 0:
×
UNCOV
1496
            log.error(f'{error_count} processing errors; see logs above')
×
1497
            log.error(goodbye)
×
1498
        else:
1499
            log.info(goodbye)
×
1500

1501
    if error_count > 0:
×
UNCOV
1502
        sys.exit(int(error_count))
×
1503
    else:
1504
        return 0
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc