Esempio n. 1
0
    def test_main(self):
        if self.data_unavailable:
            self.skipTest("Failed to download test data.")
        argstr = [
            '--fiberflat',
            self.testflat,
            '--arcfile',
            self.testarc,
            '--outfile',
            self.testout,
            '--qafile',
            self.qafile,
        ]
        args = bootscript.parse(options=argstr)
        bootscript.main(args)

        #- Ensure the PSF class can read that file
        from desispec.quicklook.qlpsf import PSF
        psf = PSF(self.testout)

        #- While we're at it, test some PSF accessor functions
        indices = np.array([0, 1])
        waves = np.array([psf.wmin, psf.wmin + 1])

        w = psf.wavelength()
        w = psf.wavelength(ispec=0)
        w = psf.wavelength(ispec=indices)
        w = psf.wavelength(ispec=indices, y=0)
        w = psf.wavelength(ispec=indices, y=indices)

        x = psf.x()
        x = psf.x(ispec=0)
        x = psf.x(ispec=indices)
        x = psf.x(ispec=None, wavelength=psf.wmin)
        x = psf.x(ispec=1, wavelength=psf.wmin)
        x = psf.x(ispec=indices, wavelength=psf.wmin)
        x = psf.x(ispec=indices, wavelength=waves)

        y = psf.y(ispec=None, wavelength=psf.wmin)
        y = psf.y(ispec=0, wavelength=psf.wmin)
        y = psf.y(ispec=indices, wavelength=psf.wmin)
        y = psf.y(ispec=indices, wavelength=waves)
Esempio n. 2
0
    def test_main(self):
        if self.data_unavailable:
            self.skipTest("Failed to download test data.")
        argstr = [
            '--fiberflat', self.testflat,
            '--arcfile', self.testarc,
            '--outfile', self.testout,
            '--qafile', self.qafile,
        ]
        args = bootscript.parse(options=argstr)
        bootscript.main(args)
        
        #- Ensure the PSF class can read that file
        from desispec.psf import PSF
        psf = PSF(self.testout)
        
        #- While we're at it, test some PSF accessor functions
        w = psf.wavelength()
        w = psf.wavelength(ispec=0)
        w = psf.wavelength(ispec=[0,1])
        w = psf.wavelength(ispec=[0,1], y=0)
        w = psf.wavelength(ispec=[0,1], y=[0,1])
        
        x = psf.x()
        x = psf.x(ispec=0)
        x = psf.x(ispec=[0,1])
        x = psf.x(ispec=None, wavelength=psf.wmin)
        x = psf.x(ispec=1, wavelength=psf.wmin)
        x = psf.x(ispec=[0,1], wavelength=psf.wmin)
        x = psf.x(ispec=[0,1], wavelength=[psf.wmin, psf.wmin+1])

        y = psf.y(ispec=None, wavelength=psf.wmin)
        y = psf.y(ispec=0, wavelength=psf.wmin)
        y = psf.y(ispec=[0,1], wavelength=psf.wmin)
        y = psf.y(ispec=[0,1], wavelength=[psf.wmin, psf.wmin+1])
        
        t = psf.invert()        
Esempio n. 3
0
def run_task(step, rawdir, proddir, grph, opts, comm=None):
    '''
    Run a single pipeline task.

    This function takes a truncated graph containing a single node
    of the specified type and the nodes representing the inputs for
    the task.

    Args:
        step (str): the pipeline step type.
        rawdir (str): the path to the raw data directory.
        proddir (str): the path to the production directory.
        grph (dict): the truncated dependency graph.
        opts (dict): the global options dictionary.
        comm (mpi4py.Comm): the optional MPI communicator to use.

    Returns:
        Nothing.
    '''

    if step not in step_file_types.keys():
        raise ValueError("step type {} not recognized".format(step))

    log = get_logger()

    # Verify that there is only a single node in the graph
    # of the desired step.  The graph should already have
    # been sliced before calling this task.
    nds = []
    for name, nd in grph.items():
        if nd['type'] in step_file_types[step]:
            nds.append(name)
    if len(nds) != 1:
        raise RuntimeError("run_task should only be called with a graph containing a single node to process")

    name = nds[0]
    node = grph[name]

    nproc = 1
    rank = 0
    if comm is not None:
        nproc = comm.size
        rank = comm.rank

    # step-specific operations

    if step == 'bootcalib':

        # The inputs to this step include *all* the arcs and flats for the
        # night.  Here we sort them into the list of arcs and the list of
        # flats, and simply choose the first one of each.
        arcs = []
        flats = []
        for input in node['in']:
            inode = grph[input]
            if inode['flavor'] == 'arc':
                arcs.append(input)
            elif inode['flavor'] == 'flat':
                flats.append(input)
        if len(arcs) == 0:
            raise RuntimeError("no arc images found!")
        if len(flats) == 0:
            raise RuntimeError("no flat images found!")
        firstarc = sorted(arcs)[0]
        firstflat = sorted(flats)[0]
        # build list of options
        arcpath = graph_path_pix(rawdir, firstarc)
        flatpath = graph_path_pix(rawdir, firstflat)
        outpath = graph_path_psfboot(proddir, name)
        qafile, qafig = qa_path(outpath)
        options = {}
        options['fiberflat'] = flatpath
        options['arcfile'] = arcpath
        options['qafile'] = qafile
        ### options['qafig'] = qafig
        options['outfile'] = outpath
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_bootcalib']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = bootcalib.parse(optarray)

        sys.stdout.flush()
        if rank == 0:
            #print("proc {} call bootcalib main".format(rank))
            #sys.stdout.flush()
            bootcalib.main(args)
            #print("proc {} returned from bootcalib main".format(rank))
            #sys.stdout.flush()
        #print("proc {} finish runtask bootcalib".format(rank))
        #sys.stdout.flush()

    elif step == 'specex':

        # get input files
        pix = []
        boot = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'psfboot':
                boot.append(input)
            elif inode['type'] == 'pix':
                pix.append(input)
        if len(boot) != 1:
            raise RuntimeError("specex needs exactly one psfboot file")
        if len(pix) == 0:
            raise RuntimeError("specex needs exactly one image file")
        bootfile = graph_path_psfboot(proddir, boot[0])
        imgfile = graph_path_pix(rawdir, pix[0])
        outfile = graph_path_psf(proddir, name)
        outdir = os.path.dirname(outfile)

        options = {}
        options['input'] = imgfile
        options['bootfile'] = bootfile
        options['output'] = outfile
        if log.getEffectiveLevel() == desispec.log.DEBUG:
            options['verbose'] = True
        if len(opts) > 0:
            extarray = option_list(opts)
            options['extra'] = " ".join(extarray)

        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_psf']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = specex.parse(optarray)
        specex.main(args, comm=comm)

    elif step == 'psfcombine':

        outfile = graph_path_psfnight(proddir, name)
        infiles = []
        for input in node['in']:
            infiles.append(graph_path_psf(proddir, input))

        if rank == 0:
            specex.mean_psf(infiles, outfile)

    elif step == 'extract':
        
        pix = []
        psf = []
        fm = []
        band = None
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'psfnight':
                psf.append(input)
            elif inode['type'] == 'pix':
                pix.append(input)
                band = inode['band']
            elif inode['type'] == 'fibermap':
                fm.append(input)
        if len(psf) != 1:
            raise RuntimeError("extraction needs exactly one psfnight file")
        if len(pix) != 1:
            raise RuntimeError("extraction needs exactly one image file")
        if len(fm) != 1:
            raise RuntimeError("extraction needs exactly one fibermap file")

        imgfile = graph_path_pix(rawdir, pix[0])
        psffile = graph_path_psfnight(proddir, psf[0])
        fmfile = graph_path_fibermap(rawdir, fm[0])
        outfile = graph_path_frame(proddir, name)

        options = {}
        options['input'] = imgfile
        options['fibermap'] = fmfile
        options['psf'] = psffile
        options['output'] = outfile

        # extract the wavelength range from the options, depending on the band

        optscopy = copy.deepcopy(opts)
        wkey = "wavelength_{}".format(band)
        wave = optscopy[wkey]
        del optscopy['wavelength_b']
        del optscopy['wavelength_r']
        del optscopy['wavelength_z']
        optscopy['wavelength'] = wave

        options.update(optscopy)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_extract_spectra']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = extract.parse(optarray)
        extract.main_mpi(args, comm=comm)
    
    elif step == 'fiberflat':

        if len(node['in']) != 1:
            raise RuntimeError('fiberflat should have only one input frame')
        framefile = graph_path_frame(proddir, node['in'][0])
        outfile = graph_path_fiberflat(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_fiberflat']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = fiberflat.parse(optarray)

        if rank == 0:
            fiberflat.main(args)
    
    elif step == 'sky':
        
        frm = []
        flat = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
        if len(frm) != 1:
            raise RuntimeError("sky needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("sky needs exactly one fiberflat file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        outfile = graph_path_sky(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_sky']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = skypkg.parse(optarray)

        if rank == 0:
            skypkg.main(args)
    
    elif step == 'stdstars':

        frm = []
        flat = []
        sky = []
        flatexp = None
        specgrph = None
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
                specgrph = inode['spec']
            elif inode['type'] == 'fiberflat':
                flat.append(input)
                flatexp = inode['id']
            elif inode['type'] == 'sky':
                sky.append(input)

        outfile = graph_path_stdstars(proddir, name)
        qafile, qafig = qa_path(outfile)
        
        framefiles = [graph_path_frame(proddir, x) for x in frm]
        skyfiles = [graph_path_sky(proddir, x) for x in sky]
        flatfiles = [graph_path_fiberflat(proddir, x) for x in flat]

        options = {}
        options['frames'] = framefiles
        options['skymodels'] = skyfiles
        options['fiberflats'] = flatfiles
        options['outfile'] = outfile
        options['ncpu'] = str(default_nproc)
        #- TODO: no QA for fitting standard stars yet
        
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_fit_stdstars']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = stdstars.parse(optarray)

        if rank == 0:
            stdstars.main(args)
    
    elif step == 'fluxcal':

        frm = []
        flat = []
        sky = []
        star = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
            elif inode['type'] == 'sky':
                sky.append(input)
            elif inode['type'] == 'stdstars':
                star.append(input)
        if len(frm) != 1:
            raise RuntimeError("fluxcal needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("fluxcal needs exactly one fiberflat file")
        if len(sky) != 1:
            raise RuntimeError("fluxcal needs exactly one sky file")
        if len(star) != 1:
            raise RuntimeError("fluxcal needs exactly one star file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        skyfile = graph_path_sky(proddir, sky[0])
        starfile = graph_path_stdstars(proddir, star[0])
        outfile = graph_path_calib(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['sky'] = skyfile
        options['models'] = starfile
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_fluxcalibration']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = fluxcal.parse(optarray)

        if rank == 0:
            fluxcal.main(args)
    
    elif step == 'procexp':
        
        frm = []
        flat = []
        sky = []
        cal = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
            elif inode['type'] == 'sky':
                sky.append(input)
            elif inode['type'] == 'calib':
                cal.append(input)
        if len(frm) != 1:
            raise RuntimeError("procexp needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("procexp needs exactly one fiberflat file")
        if len(sky) != 1:
            raise RuntimeError("procexp needs exactly one sky file")
        if len(cal) != 1:
            raise RuntimeError("procexp needs exactly one calib file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        skyfile = graph_path_sky(proddir, sky[0])
        calfile = graph_path_calib(proddir, cal[0])
        outfile = graph_path_cframe(proddir, name)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['sky'] = skyfile
        options['calib'] = calfile
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_process_exposure']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = procexp.parse(optarray)

        if rank == 0:
            procexp.main(args)
    
    elif step == 'zfind':
        brick = node['brick']
        outfile = graph_path_zbest(proddir, name)
        qafile, qafig = qa_path(outfile)
        options = {}
        options['brick'] = brick
        options['outfile'] = outfile
        #- TODO: no QA for desi_zfind yet
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_zfind']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = zfind.parse(optarray)
        zfind.main(args, comm=comm)

    else:
        raise RuntimeError("Unknown pipeline step {}".format(step))

    #sys.stdout.flush()
    if comm is not None:
        #print("proc {} hit runtask barrier".format(rank))
        #sys.stdout.flush()
        comm.barrier()
    #print("proc {} finish runtask".format(rank))
    #sys.stdout.flush()

    return
Esempio n. 4
0
def run_task(step, rawdir, proddir, grph, opts, comm=None):
    if step not in step_file_types.keys():
        raise ValueError("step type {} not recognized".format(step))

    log = get_logger()

    # Verify that there is only a single node in the graph
    # of the desired step.  The graph should already have
    # been sliced before calling this task.
    nds = []
    for name, nd in grph.items():
        if nd['type'] in step_file_types[step]:
            nds.append(name)
    if len(nds) != 1:
        raise RuntimeError("run_task should only be called with a graph containing a single node to process")

    name = nds[0]
    node = grph[name]

    nproc = 1
    rank = 0
    if comm is not None:
        nproc = comm.size
        rank = comm.rank

    # step-specific operations

    if step == 'bootcalib':

        # The inputs to this step include *all* the arcs and flats for the
        # night.  Here we sort them into the list of arcs and the list of
        # flats, and simply choose the first one of each.
        arcs = []
        flats = []
        for input in node['in']:
            inode = grph[input]
            if inode['flavor'] == 'arc':
                arcs.append(input)
            elif inode['flavor'] == 'flat':
                flats.append(input)
        if len(arcs) == 0:
            raise RuntimeError("no arc images found!")
        if len(flats) == 0:
            raise RuntimeError("no flat images found!")
        firstarc = sorted(arcs)[0]
        firstflat = sorted(flats)[0]
        # build list of options
        arcpath = graph_path_pix(rawdir, firstarc)
        flatpath = graph_path_pix(rawdir, firstflat)
        outpath = graph_path_psfboot(proddir, name)
        qafile, qafig = qa_path(outpath)
        options = {}
        options['fiberflat'] = flatpath
        options['arcfile'] = arcpath
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['outfile'] = outpath
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_bootcalib']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = bootcalib.parse(optarray)

        sys.stdout.flush()
        if rank == 0:
            #print("proc {} call bootcalib main".format(rank))
            #sys.stdout.flush()
            bootcalib.main(args)
            #print("proc {} returned from bootcalib main".format(rank))
            #sys.stdout.flush()
        #print("proc {} finish runtask bootcalib".format(rank))
        #sys.stdout.flush()

    elif step == 'specex':

        # get input files
        pix = []
        boot = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'psfboot':
                boot.append(input)
            elif inode['type'] == 'pix':
                pix.append(input)
        if len(boot) != 1:
            raise RuntimeError("specex needs exactly one psfboot file")
        if len(pix) == 0:
            raise RuntimeError("specex needs exactly one image file")
        bootfile = graph_path_psfboot(proddir, boot[0])
        imgfile = graph_path_pix(rawdir, pix[0])
        outfile = graph_path_psf(proddir, name)
        outdir = os.path.dirname(outfile)

        options = {}
        options['input'] = imgfile
        options['bootfile'] = bootfile
        options['output'] = outfile
        if log.getEffectiveLevel() == desispec.log.DEBUG:
            options['verbose'] = True
        if len(opts) > 0:
            extarray = option_list(opts)
            options['extra'] = " ".join(extarray)

        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_psf']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = specex.parse(optarray)
        specex.main(args, comm=comm)

    elif step == 'psfcombine':

        outfile = graph_path_psfnight(proddir, name)
        infiles = []
        for input in node['in']:
            infiles.append(graph_path_psf(proddir, input))

        if rank == 0:
            specex.mean_psf(infiles, outfile)

    elif step == 'extract':
        
        pix = []
        psf = []
        fm = []
        band = None
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'psfnight':
                psf.append(input)
            elif inode['type'] == 'pix':
                pix.append(input)
                band = inode['band']
            elif inode['type'] == 'fibermap':
                fm.append(input)
        if len(psf) != 1:
            raise RuntimeError("extraction needs exactly one psfnight file")
        if len(pix) != 1:
            raise RuntimeError("extraction needs exactly one image file")
        if len(fm) != 1:
            raise RuntimeError("extraction needs exactly one fibermap file")

        imgfile = graph_path_pix(rawdir, pix[0])
        psffile = graph_path_psfnight(proddir, psf[0])
        fmfile = graph_path_fibermap(rawdir, fm[0])
        outfile = graph_path_frame(proddir, name)

        options = {}
        options['input'] = imgfile
        options['fibermap'] = fmfile
        options['psf'] = psffile
        options['output'] = outfile

        # extract the wavelength range from the options, depending on the band

        optscopy = copy.deepcopy(opts)
        wkey = "wavelength_{}".format(band)
        wave = optscopy[wkey]
        del optscopy['wavelength_b']
        del optscopy['wavelength_r']
        del optscopy['wavelength_z']
        optscopy['wavelength'] = wave

        options.update(optscopy)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_extract_spectra']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = extract.parse(optarray)
        extract.main_mpi(args, comm=comm)
    
    elif step == 'fiberflat':

        if len(node['in']) != 1:
            raise RuntimeError('fiberflat should have only one input frame')
        framefile = graph_path_frame(proddir, node['in'][0])
        outfile = graph_path_fiberflat(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_fiberflat']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = fiberflat.parse(optarray)

        if rank == 0:
            fiberflat.main(args)
    
    elif step == 'sky':
        
        frm = []
        flat = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
        if len(frm) != 1:
            raise RuntimeError("sky needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("sky needs exactly one fiberflat file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        outfile = graph_path_sky(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_sky']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = skypkg.parse(optarray)

        if rank == 0:
            skypkg.main(args)
    
    elif step == 'stdstars':

        frm = []
        flat = []
        sky = []
        flatexp = None
        specgrph = None
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
                specgrph = inode['spec']
            elif inode['type'] == 'fiberflat':
                flat.append(input)
                flatexp = inode['id']
            elif inode['type'] == 'sky':
                sky.append(input)

        outfile = graph_path_stdstars(proddir, name)
        qafile, qafig = qa_path(outfile)
        
        framefiles = [graph_path_frame(proddir, x) for x in frm]
        skyfiles = [graph_path_sky(proddir, x) for x in sky]
        flatfiles = [graph_path_fiberflat(proddir, x) for x in flat]

        options = {}
        options['frames'] = framefiles
        options['skymodels'] = skyfiles
        options['fiberflats'] = flatfiles
        options['outfile'] = outfile
        options['ncpu'] = str(default_nproc)
        #- TODO: no QA for fitting standard stars yet
        
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_fit_stdstars']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = stdstars.parse(optarray)

        if rank == 0:
            stdstars.main(args)
    
    elif step == 'fluxcal':

        frm = []
        flat = []
        sky = []
        star = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
            elif inode['type'] == 'sky':
                sky.append(input)
            elif inode['type'] == 'stdstars':
                star.append(input)
        if len(frm) != 1:
            raise RuntimeError("fluxcal needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("fluxcal needs exactly one fiberflat file")
        if len(sky) != 1:
            raise RuntimeError("fluxcal needs exactly one sky file")
        if len(star) != 1:
            raise RuntimeError("fluxcal needs exactly one star file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        skyfile = graph_path_sky(proddir, sky[0])
        starfile = graph_path_stdstars(proddir, star[0])
        outfile = graph_path_calib(proddir, name)
        qafile, qafig = qa_path(outfile)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['qafile'] = qafile
        options['qafig'] = qafig
        options['sky'] = skyfile
        options['models'] = starfile
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_compute_fluxcalibration']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = fluxcal.parse(optarray)

        if rank == 0:
            fluxcal.main(args)
    
    elif step == 'procexp':
        
        frm = []
        flat = []
        sky = []
        cal = []
        for input in node['in']:
            inode = grph[input]
            if inode['type'] == 'frame':
                frm.append(input)
            elif inode['type'] == 'fiberflat':
                flat.append(input)
            elif inode['type'] == 'sky':
                sky.append(input)
            elif inode['type'] == 'calib':
                cal.append(input)
        if len(frm) != 1:
            raise RuntimeError("procexp needs exactly one frame file")
        if len(flat) != 1:
            raise RuntimeError("procexp needs exactly one fiberflat file")
        if len(sky) != 1:
            raise RuntimeError("procexp needs exactly one sky file")
        if len(cal) != 1:
            raise RuntimeError("procexp needs exactly one calib file")

        framefile = graph_path_frame(proddir, frm[0])
        flatfile = graph_path_fiberflat(proddir, flat[0])
        skyfile = graph_path_sky(proddir, sky[0])
        calfile = graph_path_calib(proddir, cal[0])
        outfile = graph_path_cframe(proddir, name)

        options = {}
        options['infile'] = framefile
        options['fiberflat'] = flatfile
        options['sky'] = skyfile
        options['calib'] = calfile
        options['outfile'] = outfile
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_process_exposure']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = procexp.parse(optarray)

        if rank == 0:
            procexp.main(args)
    
    elif step == 'zfind':
        brick = node['brick']
        outfile = graph_path_zbest(proddir, name)
        qafile, qafig = qa_path(outfile)
        options = {}
        options['brick'] = brick
        options['outfile'] = outfile
        #- TODO: no QA for desi_zfind yet
        options.update(opts)
        optarray = option_list(options)

        # at debug level, write out the equivalent commandline
        com = ['RUN', 'desi_zfind']
        com.extend(optarray)
        log.debug(" ".join(com))

        args = zfind.parse(optarray)
        zfind.main(args, comm=comm)

    else:
        raise RuntimeError("Unknown pipeline step {}".format(step))

    #sys.stdout.flush()
    if comm is not None:
        #print("proc {} hit runtask barrier".format(rank))
        #sys.stdout.flush()
        comm.barrier()
    #print("proc {} finish runtask".format(rank))
    #sys.stdout.flush()

    return