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

desihub / desispec / 8397945773

23 Mar 2024 12:22AM UTC coverage: 28.132% (+3.0%) from 25.113%
8397945773

Pull #2187

github

akremin
bug fixes in old sub scripts
Pull Request #2187: Introduce desi_proc_night to unify and simplify processing scripts

768 of 1152 new or added lines in 21 files covered. (66.67%)

1071 existing lines in 14 files now uncovered.

13184 of 46864 relevant lines covered (28.13%)

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

116
    #- Start timer; only print log messages from rank 0 (others are silent)
117
    timer = desiutil.timer.Timer(silent=(rank>0))
×
118

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

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

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

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

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

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

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

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

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

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

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

179
        sys.exit(1)
×
180

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

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

189
    timer.stop('preflight')
×
190

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

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

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

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

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

237
    #-------------------------------------------------------------------------
238
    #- Proceeding with running
239

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

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

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

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

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

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

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

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

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

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

289
        timer.stop('nightlybias')
×
290

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

295
        timer.start('nightlycte')
×
296

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

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

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

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

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

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

314
        timer.stop('nightlycte')
×
315

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

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

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

333
        sys.exit(error_count)
×
334

335

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

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

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

353
    timer.start('fibermap')
×
354

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

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

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

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

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

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

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

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

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

406
        sys.exit(13)
×
407

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

412
    timer.stop('fibermap')
×
413

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

432
            inputs = [args.input]
×
433

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

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

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

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

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

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

484
    timer.stop('findpsf')
×
485

486

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

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

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

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

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

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

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

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

531
    #-------------------------------------------------------------------------
532
    #- Traceshift
533

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

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

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

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

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

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

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

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

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

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

589
        timer.start('arc_traceshift')
×
590

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

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

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

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

620
        timer.start('psf')
×
621

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

897
            comm.barrier()
×
898

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

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

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

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

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

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

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

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

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

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

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

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

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

966
    #-------------------------------------------------------------------------
967
    #- Fiberflat
968

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

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

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

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

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

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

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

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

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

1042
        timer.stop('find_fiberflat')
×
1043

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

1048
        timer.start('fiberflat_humidity_correction')
×
1049

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1242
    #-------------------------------------------------------------------------
1243
    #- Standard Star Fitting
1244

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

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

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

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

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

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

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

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

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

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

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

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

1329
    # -------------------------------------------------------------------------
1330
    # - Flux calibration
1331

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

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

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

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

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

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

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

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

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

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

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

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

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

1405
    #-------------------------------------------------------------------------
1406
    #- Applying flux calibration
1407

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

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

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

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

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

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

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

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

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

1448
        timer.stop('applycalib')
×
1449

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

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

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

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

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

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

1479
        timer.stop('exposure_qa')
×
1480

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

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

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

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

1502
    if error_count > 0:
×
UNCOV
1503
        sys.exit(int(error_count))
×
1504
    else:
1505
        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