Exemplo n.º 1
0
def test_init():
    """ Run the parameter setup script
    """
    # Null
    sobjs1 = specobjs.SpecObjs()

    # With a few objs
    sobjs2 = specobjs.SpecObjs([sobj1,sobj2])
Exemplo n.º 2
0
def test_add_rm(sobj1, sobj2, sobj3):
    sobjs = specobjs.SpecObjs([sobj1, sobj2])
    sobjs.add_sobj(sobj3)
    assert sobjs.nobj == 3
    # Remove
    sobjs.remove_sobj(2)
    assert len(sobjs.specobjs) == 2

    # Numpy 18
    sobjs1 = specobjs.SpecObjs()
    sobjs2 = specobjs.SpecObjs()
    sobjs2.add_sobj(sobjs1)
Exemplo n.º 3
0
def test_io(sobj1, sobj2, sobj3, sobj4):
    sobjs = specobjs.SpecObjs([sobj1, sobj2, sobj3, sobj4])
    sobjs[0]['BOX_WAVE'] = np.arange(1000).astype(float)
    sobjs[1]['BOX_WAVE'] = np.arange(1000).astype(float)
    sobjs[2]['BOX_WAVE'] = np.arange(1000).astype(float)
    #sobjs[0]['BOX_COUNTS'] = np.ones_like(sobjs[0].BOX_WAVE)  # This tests single array
    sobjs[1]['BOX_COUNTS'] = np.ones_like(sobjs[0].BOX_WAVE)
    sobjs[2]['BOX_COUNTS'] = np.ones_like(sobjs[0].BOX_WAVE)
    # Detector
    sobjs[0]['DETECTOR'] = tstutils.get_kastb_detector()
    tmp = tstutils.get_kastb_detector()

    tmp['det'] = 2
    sobjs[1]['DETECTOR'] = tmp
    # Write
    header = fits.PrimaryHDU().header
    header['TST'] = 'TEST'
    ofile = data_path('tst_specobjs.fits')
    if os.path.isfile(ofile):
        os.remove(ofile)
    sobjs.write_to_fits(header, ofile, overwrite=False)
    # Read
    hdul = fits.open(ofile)
    assert len(hdul) == 7  # Primary + 4 Obj + 2 Detectors
    assert hdul[0].header['NSPEC'] == 4
    hdul.close()
    #
    _sobjs = specobjs.SpecObjs.from_fitsfile(ofile)
    assert _sobjs.nobj == 4
    assert np.array_equal(sobjs[0].BOX_WAVE, _sobjs[0].BOX_WAVE)
    assert np.array_equal(sobjs[1].BOX_WAVE, _sobjs[1].BOX_WAVE)
    _sobjs.write_to_fits(header, ofile, overwrite=True)

    # Detector
    assert _sobjs[0].DETECTOR is not None, '1st object started with Detector'
    assert _sobjs[
        1].DETECTOR is not None, '2nd object has DET=1 so should get decorated'
    assert _sobjs[2].DETECTOR is None

    # Now try updates!
    sobjs1 = specobjs.SpecObjs([sobj1])
    sobjs[0]['BOX_WAVE'] = np.arange(2000).astype(float)
    sobjs1[0]['DETECTOR'] = tstutils.get_kastb_detector()
    header1 = fits.PrimaryHDU().header
    sobjs1.write_to_fits(header1, ofile, overwrite=True, update_det=1)

    # Test
    _sobjs1 = specobjs.SpecObjs.from_fitsfile(ofile)
    assert _sobjs1.nobj == 3
    assert _sobjs1[2].BOX_WAVE.size == 2000
    os.remove(ofile)
Exemplo n.º 4
0
def test_add_rm():
    sobjs = specobjs.SpecObjs([sobj1, sobj2])
    sobjs.add_sobj(sobj3)
    assert sobjs.nobj == 3
    # Remove
    sobjs.remove_sobj(2)
    assert len(sobjs.specobjs) == 2
Exemplo n.º 5
0
def test_geocorrect(fitstbl):
    """
    """

    # Specobj (wrap in a list to mimic a slit)
    npix = 1000
    sobj = specobj.SpecObj('MultiSlit', 1, SLITID=0)
    sobj.BOX_WAVE = np.linspace(4000., 6000., npix)
    sobj.BOX_COUNTS = 50. * (sobj.BOX_WAVE / 5000.)**-1.
    sobj.BOX_COUNTS_IVAR = 1. / sobj.BOX_COUNTS.copy()
    # SpecObjs
    specObjs = specobjs.SpecObjs()
    specObjs.add_sobj(sobj)
    scidx = 5
    obstime = Time(fitstbl['mjd'][scidx],
                   format='mjd')  #'%Y-%m-%dT%H:%M:%S.%f')
    maskslits = np.array([False] * specObjs.nobj)
    radec = ltu.radec_to_coord((fitstbl["ra"][scidx], fitstbl["dec"][scidx]))

    helio, hel_corr = wave.geomotion_correct(specObjs, radec, obstime,
                                             maskslits, lon, lat, alt,
                                             'heliocentric')
    assert np.isclose(helio, -9.17461338,
                      rtol=1e-5)  # Checked against x_keckhelio
    #assert np.isclose(helio, -9.3344957, rtol=1e-5)  # Original
    assert np.isclose(specObjs[0].BOX_WAVE[0], 3999.877589008, rtol=1e-8)
Exemplo n.º 6
0
def test_save1d_fits():
    """ save1d to FITS and HDF5
    """
    # Init
    fitstbl = dummy_fitstbl(spectro_name='shane_kast_blue', directory=data_path(''))
    sobj = mk_specobj()
    specObjs = specobjs.SpecObjs([sobj])
    spectrograph = util.load_spectrograph('shane_kast_blue')
    # Write to FITS
    basename = 'test'
    outfile = data_path('') + 'spec1d_{:s}.fits'.format(basename)
    save.save_1d_spectra_fits(specObjs, fitstbl[5], spectrograph, outfile)
Exemplo n.º 7
0
def test_set(sobj1, sobj2, sobj3):
    sobjs = specobjs.SpecObjs([sobj1, sobj2, sobj3])
    # All
    sobjs.DET = 'DET03'
    assert np.all(sobjs[:].DET == np.array(['DET03', 'DET03', 'DET03']))
    sobjs[:].DET = 'DET04'
    assert np.all(sobjs[:].DET == np.array(['DET04', 'DET04', 'DET04']))
    # Slice
    sobjs[1:2].DET = 'DET02'
    assert sobjs.DET[1] == 'DET02'
    # With logic
    det2 = sobjs.DET == 'DET02'
    sobjs[det2].PYPELINE = 'BLAH'
    assert sobjs.PYPELINE[1] == 'BLAH'
    assert sobjs.PYPELINE[0] == 'MultiSlit'
Exemplo n.º 8
0
def test_set(sobj1, sobj2, sobj3):
    sobjs = specobjs.SpecObjs([sobj1, sobj2, sobj3])
    # All
    sobjs.DET = 3
    assert np.all(sobjs[:].DET == np.array([3, 3, 3]))
    sobjs[:].DET = 4
    assert np.all(sobjs[:].DET == np.array([4, 4, 4]))
    # Slice
    sobjs[1:2].DET = 2
    assert sobjs.DET[1] == 2
    # With logic
    det2 = sobjs.DET == 2
    sobjs[det2].PYPELINE = 'BLAH'
    assert sobjs.PYPELINE[1] == 'BLAH'
    assert sobjs.PYPELINE[0] == 'MultiSlit'
Exemplo n.º 9
0
def test_set():
    sobjs = specobjs.SpecObjs([sobj1,sobj2,sobj3])
    # All
    sobjs['det'] = 3
    assert np.all(sobjs[:].det == np.array([3,3,3]))
    # Slice
    sobjs[1:2]['det'] = 2
    assert sobjs.det[1] == 2
    # Under the hood
    sobjs.set(0, 'det', 3)
    sobjs.set(slice(1,2), 'det', 2)
    # Test
    assert np.all(sobjs[:].det == np.array([3,2,3]))
    # Hennawi test
    idx = sobjs.det == 3
    sobjs[idx]['det'] = 1
Exemplo n.º 10
0
def test_geocorrect(fitstbl):
    """
    """
    # Spectrograph
    # (KBW) Had to change this to keck to match the telecope parameters,
    # then just changed to use definitions above directly.
#    spectrograph = load_spectrograph('keck_lris_blue')

    # Specobjs (wrap in a list to mimic a slit)
    sobj_list = specobjs.dummy_specobj((2048,2048), extraction=True)
    specObjs = specobjs.SpecObjs(sobj_list)
    scidx = 5
    obstime = Time(fitstbl['mjd'][scidx], format='mjd')#'%Y-%m-%dT%H:%M:%S.%f')
    maskslits = np.array([False]*specObjs.nobj)
    radec = ltu.radec_to_coord((fitstbl["ra"][scidx], fitstbl["dec"][scidx]))

    helio, hel_corr = wave.geomotion_correct(specObjs, radec, obstime, maskslits,
                                               lon, lat, alt, 'heliocentric')
    assert np.isclose(helio, -9.17461338, rtol=1e-5)  # Checked against x_keckhelio
    #assert np.isclose(helio, -9.3344957, rtol=1e-5)  # Original
    assert np.isclose(specObjs[0].boxcar['WAVE'][0].value, 3999.877589008, rtol=1e-8)
def write_science(sci_specobjs, sci_header, outfile):
    """
    Write the flux-calibrated science spectra

    Parameters
    ----------
    outfile : str

    Returns
    -------

    """
    if len(sci_specobjs) == 0:
        msgs.warn("No science spectra to write to disk!")
    #
    if 'VEL-TYPE' in sci_header.keys():
        helio_dict = dict(refframe=sci_header['VEL-TYPE'],
                          vel_correction=sci_header['VEL'])
    else:
        helio_dict = None
    telescope = None
    if 'LON-OBS' in sci_header.keys():
        telescope = TelescopePar(longitude=sci_header['LON-OBS'],
                                 latitude=sci_header['LAT-OBS'],
                                 elevation=sci_header['ALT-OBS'])
    # KLUDGE ME
    if isinstance(sci_specobjs, list):
        specObjs = specobjs.SpecObjs(sci_specobjs)
    elif isinstance(sci_specobjs, specobjs.SpecObjs):
        specObjs = sci_specobjs
    else:
        msgs.error("BAD INPUT")
    save.save_1d_spectra_fits(specObjs,
                              sci_header,
                              'ECHELLE',
                              outfile,
                              helio_dict=helio_dict,
                              telescope=telescope,
                              overwrite=True)
Exemplo n.º 12
0
    def write_science(self, outfile):
        """
        Write the flux-calibrated science spectra

        Parameters
        ----------
        outfile : str

        Returns
        -------

        """
        if len(self.sci_specobjs) == 0:
            msgs.warn("No science spectra to write to disk!")
        #
        if 'VEL-TYPE' in self.sci_header.keys():
            helio_dict = dict(refframe=self.sci_header['VEL-TYPE'],
                              vel_correction=self.sci_header['VEL'])
        else:
            helio_dict = None
        # KLUDGE ME
        if isinstance(self.sci_specobjs, list):
            specObjs = specobjs.SpecObjs(self.sci_specobjs)
        elif isinstance(self.sci_specobjs, specobjs.SpecObjs):
            specObjs = self.sci_specobjs
        else:
            msgs.error("BAD INPUT")

        #save_1d_spectra_fits(specObjs, header, spectrograph, outfile, helio_dict=None, overwrite=True,update_det=None)
        save.save_1d_spectra_fits(specObjs,
                                  self.sci_header,
                                  self.spectrograph,
                                  outfile,
                                  helio_dict=helio_dict,
                                  overwrite=True)
        # Step
        self.steps.append(inspect.stack()[0][3])
Exemplo n.º 13
0
    def reduce_exposure(self, frames, bg_frames=None, std_outfile=None):
        """
        Reduce a single exposure

        Args:
            frame (:obj:`int`):
                0-indexed row in :attr:`fitstbl` with the frame to
                reduce.
            bg_frames (:obj:`list`, optional):
                List of frame indices for the background.
            std_outfile (:obj:`str`, optional):
                File with a previously reduced standard spectrum from
                PypeIt.

        Returns:
            dict: The dictionary containing the primary outputs of
            extraction.

        """

        # TODO:
        # - change doc string to reflect that more than one frame can be
        #   provided

        # if show is set, clear the ginga channels at the start of each new sci_ID
        if self.show:
            # TODO: Put this in a try/except block?
            display.clear_all()

        has_bg = True if bg_frames is not None and len(
            bg_frames) > 0 else False

        # Is this an IR reduction?
        # TODO: Why specific to IR?
        self.ir_redux = True if has_bg else False

        # Container for all the Spec2DObj
        all_spec2d = spec2dobj.AllSpec2DObj()
        all_spec2d['meta']['ir_redux'] = self.ir_redux

        # TODO -- Should we reset/regenerate self.slits.mask for a new exposure

        all_specobjs = specobjs.SpecObjs()

        # Print status message
        msgs_string = 'Reducing target {:s}'.format(
            self.fitstbl['target'][frames[0]]) + msgs.newline()
        # TODO: Print these when the frames are actually combined,
        # backgrounds are used, etc?
        msgs_string += 'Combining frames:' + msgs.newline()
        for iframe in frames:
            msgs_string += '{0:s}'.format(
                self.fitstbl['filename'][iframe]) + msgs.newline()
        msgs.info(msgs_string)
        if has_bg:
            bg_msgs_string = ''
            for iframe in bg_frames:
                bg_msgs_string += '{0:s}'.format(
                    self.fitstbl['filename'][iframe]) + msgs.newline()
            bg_msgs_string = msgs.newline(
            ) + 'Using background from frames:' + msgs.newline(
            ) + bg_msgs_string
            msgs.info(bg_msgs_string)

        # Find the detectors to reduce
        detectors = PypeIt.select_detectors(
            detnum=self.par['rdx']['detnum'],
            slitspatnum=self.par['rdx']['slitspatnum'],
            ndet=self.spectrograph.ndet)
        if len(detectors) != self.spectrograph.ndet:
            msgs.warn('Not reducing detectors: {0}'.format(' '.join([
                str(d) for d in set(np.arange(self.spectrograph.ndet)) -
                set(detectors)
            ])))

        # Loop on Detectors
        # TODO: Attempt to put in a multiprocessing call here?
        for self.det in detectors:
            msgs.info("Working on detector {0}".format(self.det))
            # Instantiate Calibrations class
            self.caliBrate = calibrations.Calibrations.get_instance(
                self.fitstbl,
                self.par['calibrations'],
                self.spectrograph,
                self.calibrations_path,
                qadir=self.qa_path,
                reuse_masters=self.reuse_masters,
                show=self.show,
                slitspat_num=self.par['rdx']['slitspatnum'])
            # These need to be separate to accomodate COADD2D
            self.caliBrate.set_config(frames[0], self.det,
                                      self.par['calibrations'])
            self.caliBrate.run_the_steps()
            # Extract
            # TODO: pass back the background frame, pass in background
            # files as an argument. extract one takes a file list as an
            # argument and instantiates science within
            all_spec2d[self.det], tmp_sobjs \
                    = self.reduce_one(frames, self.det, bg_frames, std_outfile=std_outfile)
            # Hold em
            if tmp_sobjs.nobj > 0:
                all_specobjs.add_sobj(tmp_sobjs)
            # JFH TODO write out the background frame?

            # TODO -- Save here?  Seems like we should.  Would probably need to use update_det=True

        # Return
        return all_spec2d, all_specobjs
Exemplo n.º 14
0
def test_access(sobj1, sobj2):
    sobjs = specobjs.SpecObjs([sobj1, sobj2])
    #
    assert sobjs[0]['PYPELINE'] == 'MultiSlit'
    assert len(sobjs['PYPELINE']) == 2
Exemplo n.º 15
0
def test_access():
    sobjs = specobjs.SpecObjs([sobj1,sobj2])
    #
    assert sobjs[0]['shape'] == shape
    assert len(sobjs['shape']) == 2
Exemplo n.º 16
0
    def reduce_exposure(self, frames, bg_frames=None, std_outfile=None):
        """
        Reduce a single exposure

        Args:
            frames (:obj:`list`):
                List of 0-indexed rows in :attr:`fitstbl` with the frames to
                reduce.
            bg_frames (:obj:`list`, optional):
                List of frame indices for the background.
            std_outfile (:obj:`str`, optional):
                File with a previously reduced standard spectrum from
                PypeIt.

        Returns:
            dict: The dictionary containing the primary outputs of
            extraction.

        """

        # if show is set, clear the ginga channels at the start of each new sci_ID
        if self.show:
            # TODO: Put this in a try/except block?
            display.clear_all(allow_new=True)

        has_bg = True if bg_frames is not None and len(
            bg_frames) > 0 else False
        # Is this an b/g subtraction reduction?
        if has_bg:
            self.bkg_redux = True
            # The default is to find_negative objects if the bg_frames are classified as "science", and to not find_negative
            # objects if the bg_frames are classified as "sky". This can be explicitly overridden if
            # par['reduce']['findobj']['find_negative'] is set to something other than the default of None.
            self.find_negative = (('science' in self.fitstbl['frametype'][bg_frames[0]]) |
                                  ('standard' in self.fitstbl['frametype'][bg_frames[0]]))\
                if self.par['reduce']['findobj']['find_negative'] is None else self.par['reduce']['findobj']['find_negative']
        else:
            self.bkg_redux = False
            self.find_negative = False

        # Container for all the Spec2DObj
        all_spec2d = spec2dobj.AllSpec2DObj()
        all_spec2d['meta']['bkg_redux'] = self.bkg_redux
        all_spec2d['meta']['find_negative'] = self.find_negative
        # TODO -- Should we reset/regenerate self.slits.mask for a new exposure

        # container for specobjs during first loop (objfind)
        all_specobjs_objfind = specobjs.SpecObjs()
        # container for specobjs during second loop (extraction)
        all_specobjs_extract = specobjs.SpecObjs()
        # list of global_sky obtained during objfind and used in extraction
        initial_sky_list = []
        # list of sciImg
        sciImg_list = []
        # List of detectors with successful calibration
        calibrated_det = []
        # list of successful MasterSlits calibrations to be used in the extraction loop
        calib_slits = []
        # List of objFind objects
        objFind_list = []

        # Print status message
        msgs_string = 'Reducing target {:s}'.format(
            self.fitstbl['target'][frames[0]]) + msgs.newline()
        # TODO: Print these when the frames are actually combined,
        # backgrounds are used, etc?
        msgs_string += 'Combining frames:' + msgs.newline()
        for iframe in frames:
            msgs_string += '{0:s}'.format(
                self.fitstbl['filename'][iframe]) + msgs.newline()
        msgs.info(msgs_string)
        if has_bg:
            bg_msgs_string = ''
            for iframe in bg_frames:
                bg_msgs_string += '{0:s}'.format(
                    self.fitstbl['filename'][iframe]) + msgs.newline()
            bg_msgs_string = msgs.newline(
            ) + 'Using background from frames:' + msgs.newline(
            ) + bg_msgs_string
            msgs.info(bg_msgs_string)

        # Find the detectors to reduce
        subset = self.par['rdx']['slitspatnum'] if self.par['rdx']['slitspatnum'] is not None \
                    else self.par['rdx']['detnum']
        detectors = self.spectrograph.select_detectors(subset=subset)
        msgs.info(f'Detectors to work on: {detectors}')

        # Loop on Detectors
        # TODO: Attempt to put in a multiprocessing call here?
        # objfind
        for self.det in detectors:
            msgs.info("Working on detector {0}".format(self.det))
            # run calibration
            self.caliBrate = self.calib_one(frames, self.det)
            if not self.caliBrate.success:
                msgs.warn(
                    f'Calibrations for detector {self.det} were unsuccessful!  The step '
                    f'that failed was {self.caliBrate.failed_step}.  Continuing by '
                    f'skipping this detector.')
                continue

            # we save only the detectors that had a successful calibration,
            # and we use only those in the extract loop below
            calibrated_det.append(self.det)
            # we also save the successful MasterSlits calibrations because they are used and modified
            # in the slitmask stuff in between the two loops
            calib_slits.append(self.caliBrate.slits)
            # global_sky, skymask and sciImg are needed in the extract loop
            initial_sky, sobjs_obj, sciImg, objFind = self.objfind_one(
                frames, self.det, bg_frames, std_outfile=std_outfile)
            if len(sobjs_obj) > 0:
                all_specobjs_objfind.add_sobj(sobjs_obj)
            initial_sky_list.append(initial_sky)
            sciImg_list.append(sciImg)
            objFind_list.append(objFind)

        # slitmask stuff
        if self.par['reduce']['slitmask']['assign_obj']:
            # get object positions from slitmask design and slitmask offsets for all the detectors
            spat_flexure = np.array([ss.spat_flexure for ss in sciImg_list])
            # Grab platescale with binning
            bin_spec, bin_spat = parse.parse_binning(self.binning)
            platescale = np.array(
                [ss.detector.platescale * bin_spat for ss in sciImg_list])
            # get the dither offset if available
            if self.par['reduce']['slitmask']['use_dither_offset']:
                dither = self.spectrograph.parse_dither_pattern(
                    [self.fitstbl.frame_paths(frames[0])])
                dither_off = dither[2][0] if dither is not None else None
            else:
                dither_off = None
            calib_slits = slittrace.get_maskdef_objpos_offset_alldets(
                all_specobjs_objfind,
                calib_slits,
                spat_flexure,
                platescale,
                self.par['calibrations']['slitedges']['det_buffer'],
                self.par['reduce']['slitmask'],
                dither_off=dither_off)
            # determine if slitmask offsets exist and compute an average offsets over all the detectors
            calib_slits = slittrace.average_maskdef_offset(
                calib_slits, platescale[0],
                self.spectrograph.list_detectors(
                    mosaic='MSC' in calib_slits[0].detname))
            # slitmask design matching and add undetected objects
            all_specobjs_objfind = slittrace.assign_addobjs_alldets(
                all_specobjs_objfind, calib_slits, spat_flexure, platescale,
                self.par['reduce']['slitmask'],
                self.par['reduce']['findobj']['find_fwhm'])

        # Extract
        for i, self.det in enumerate(calibrated_det):
            # re-run (i.e., load) calibrations
            self.caliBrate = self.calib_one(frames, self.det)
            self.caliBrate.slits = calib_slits[i]

            detname = sciImg_list[i].detector.name

            # TODO: pass back the background frame, pass in background
            # files as an argument. extract one takes a file list as an
            # argument and instantiates science within
            if all_specobjs_objfind.nobj > 0:
                all_specobjs_on_det = all_specobjs_objfind[
                    all_specobjs_objfind.DET == detname]
            else:
                all_specobjs_on_det = all_specobjs_objfind

            # Extract
            all_spec2d[detname], tmp_sobjs \
                    = self.extract_one(frames, self.det, sciImg_list[i], objFind_list[i],
                                       initial_sky_list[i], all_specobjs_on_det)
            # Hold em
            if tmp_sobjs.nobj > 0:
                all_specobjs_extract.add_sobj(tmp_sobjs)
            # JFH TODO write out the background frame?

            # TODO -- Save here?  Seems like we should.  Would probably need to use update_det=True

        # Return
        return all_spec2d, all_specobjs_extract
Exemplo n.º 17
0
    def find_objects_pypeline(self,
                              image,
                              std_trace=None,
                              manual_extract_dict=None,
                              show_peaks=False,
                              show_fits=False,
                              show_trace=False,
                              show=False,
                              debug=False):
        """
        Pipeline specific find objects routine

        Args:
            image (np.ndarray):
            std_trace (np.ndarray, optional):
            manual_extract_dict (dict, optional):
            show_peaks (bool, optional):
              Generate QA showing peaks identified by object finding
            show_fits (bool, optional):
              Generate QA  showing fits to traces
            show_trace (bool, optional):
              Generate QA  showing traces identified. Requires an open ginga RC modules window
            show (bool, optional):
            debug (bool, optional):

        Returns:
            tuple:
                specobjs : Specobjs object
                    Container holding Specobj objects
                nobj (int):
                    Number of objects identified
                skymask : ndarray
                    Boolean image indicating which pixels are useful for global sky subtraction

        """
        gdslits = np.where(np.invert(self.reduce_bpm))[0]

        # create the ouptut image for skymask
        skymask = np.zeros_like(image, dtype=bool)
        # Instantiate the specobjs container
        sobjs = specobjs.SpecObjs()

        # Loop on slits
        for slit_idx in gdslits:
            slit_spat = self.slits.spat_id[slit_idx]
            qa_title = "Finding objects on slit # {:d}".format(slit_spat)
            msgs.info(qa_title)
            thismask = self.slitmask == slit_spat
            inmask = (self.sciImg.fullmask == 0) & thismask
            # Find objects
            specobj_dict = {
                'SLITID': slit_spat,
                'DET': self.det,
                'OBJTYPE': self.objtype,
                'PYPELINE': self.pypeline
            }

            # TODO we need to add QA paths and QA hooks. QA should be
            # done through objfind where all the relevant information
            # is. This will be a png file(s) per slit.

            sobjs_slit, skymask[thismask] = \
                    extract.objfind(image, thismask,
                                self.slits_left[:,slit_idx],
                                self.slits_right[:,slit_idx],
                                inmask=inmask, ir_redux=self.ir_redux,
                                ncoeff=self.par['reduce']['findobj']['trace_npoly'],
                                std_trace=std_trace,
                                sig_thresh=self.par['reduce']['findobj']['sig_thresh'],
                                hand_extract_dict=manual_extract_dict,
                                specobj_dict=specobj_dict, show_peaks=show_peaks,
                                show_fits=show_fits, show_trace=show_trace,
                                trim_edg=self.par['reduce']['findobj']['find_trim_edge'],
                                cont_fit=self.par['reduce']['findobj']['find_cont_fit'],
                                npoly_cont=self.par['reduce']['findobj']['find_npoly_cont'],
                                fwhm=self.par['reduce']['findobj']['find_fwhm'],
                                maxdev=self.par['reduce']['findobj']['find_maxdev'],
                                qa_title=qa_title, nperslit=self.par['reduce']['findobj']['maxnumber'],
                                debug_all=debug)
            sobjs.add_sobj(sobjs_slit)

        # Steps
        self.steps.append(inspect.stack()[0][3])
        if show:
            self.show('image',
                      image=image * (self.sciImg.fullmask == 0),
                      chname='objfind',
                      sobjs=sobjs,
                      slits=True)

        # Return
        return sobjs, len(sobjs), skymask
Exemplo n.º 18
0
    def compute_offsets(self):

        objid_bri, slitidx_bri, spatid_bri, snr_bar_bri = self.get_brightest_obj(self.stack_dict['specobjs_list'],
                                                                    self.spat_ids)
        msgs.info('Determining offsets using brightest object on slit: {:d} with avg SNR={:5.2f}'.format(spatid_bri,np.mean(snr_bar_bri)))
        thismask_stack = self.stack_dict['slitmask_stack'] == spatid_bri
        trace_stack_bri = np.zeros((self.nspec, self.nexp))
        # TODO Need to think abbout whether we have multiple tslits_dict for each exposure or a single one
        for iexp in range(self.nexp):
            trace_stack_bri[:,iexp] = self.stack_dict['slits_list'][iexp].center[:,slitidx_bri]
#            trace_stack_bri[:,iexp] = (self.stack_dict['tslits_dict_list'][iexp]['slit_left'][:,slitid_bri] +
#                                       self.stack_dict['tslits_dict_list'][iexp]['slit_righ'][:,slitid_bri])/2.0
        # Determine the wavelength grid that we will use for the current slit/order
        wave_bins = coadd.get_wave_bins(thismask_stack, self.stack_dict['waveimg_stack'], self.wave_grid)
        dspat_bins, dspat_stack = coadd.get_spat_bins(thismask_stack, trace_stack_bri)

        sci_list = [self.stack_dict['sciimg_stack'] - self.stack_dict['skymodel_stack']]
        var_list = []

        msgs.info('Rebinning Images')
        sci_list_rebin, var_list_rebin, norm_rebin_stack, nsmp_rebin_stack = coadd.rebin2d(
            wave_bins, dspat_bins, self.stack_dict['waveimg_stack'], dspat_stack, thismask_stack,
            (self.stack_dict['mask_stack'] == 0), sci_list, var_list)
        thismask = np.ones_like(sci_list_rebin[0][0,:,:],dtype=bool)
        nspec_pseudo, nspat_pseudo = thismask.shape
        slit_left = np.full(nspec_pseudo, 0.0)
        slit_righ = np.full(nspec_pseudo, nspat_pseudo)
        inmask = norm_rebin_stack > 0
        traces_rect = np.zeros((nspec_pseudo, self.nexp))
        sobjs = specobjs.SpecObjs()
        #specobj_dict = {'setup': 'unknown', 'slitid': 999, 'orderindx': 999, 'det': self.det, 'objtype': 'unknown',
        #                'pypeline': 'MultiSLit' + '_coadd_2d'}
        for iexp in range(self.nexp):
            sobjs_exp, _ = extract.objfind(sci_list_rebin[0][iexp,:,:], thismask, slit_left, slit_righ,
                                           inmask=inmask[iexp,:,:], ir_redux=self.ir_redux,
                                           fwhm=self.par['reduce']['findobj']['find_fwhm'],
                                           trim_edg=self.par['reduce']['findobj']['find_trim_edge'],
                                           npoly_cont=self.par['reduce']['findobj']['find_npoly_cont'],
                                           maxdev=self.par['reduce']['findobj']['find_maxdev'],
                                           ncoeff=3, sig_thresh=self.par['reduce']['findobj']['sig_thresh'], nperslit=1,
                                           find_min_max=self.par['reduce']['findobj']['find_min_max'],
                                           show_trace=self.debug_offsets, show_peaks=self.debug_offsets)
            sobjs.add_sobj(sobjs_exp)
            traces_rect[:, iexp] = sobjs_exp.TRACE_SPAT
        # Now deterimine the offsets. Arbitrarily set the zeroth trace to the reference
        med_traces_rect = np.median(traces_rect,axis=0)
        offsets = med_traces_rect[0] - med_traces_rect
        # Print out a report on the offsets
        msg_string = msgs.newline()  + '---------------------------------------------'
        msg_string += msgs.newline() + ' Summary of offsets for highest S/N object   '
        msg_string += msgs.newline() + '         found on slitid = {:d}              '.format(spatid_bri)
        msg_string += msgs.newline() + '---------------------------------------------'
        msg_string += msgs.newline() + '           exp#      offset                  '
        for iexp, off in enumerate(offsets):
            msg_string += msgs.newline() + '            {:d}        {:5.2f}'.format(iexp, off)

        msg_string += msgs.newline() + '-----------------------------------------------'
        msgs.info(msg_string)
        if self.debug_offsets:
            for iexp in range(self.nexp):
                plt.plot(traces_rect[:, iexp], linestyle='--', label='original trace')
                plt.plot(traces_rect[:, iexp] + offsets[iexp], label='shifted traces')
                plt.legend()
            plt.show()

        return objid_bri, spatid_bri, snr_bar_bri, offsets
Exemplo n.º 19
0
def extinction_correction_tester(algorithm):
    spec1d_file = data_path('spec1d_test.fits')
    sens_file = data_path('sens_test.fits')

    if os.path.isfile(spec1d_file):
        os.remove(spec1d_file)
    if os.path.isfile(sens_file):
        os.remove(sens_file)

    # make a bogus spectrum that has N_lam = 1
    wave = np.linspace(4000, 6000)
    counts = np.ones_like(wave)
    ivar = np.ones_like(wave)
    sobj = specobj.SpecObj.from_arrays('MultiSlit', wave, counts, ivar)
    sobjs = specobjs.SpecObjs([sobj])

    # choice of PYP_SPEC, DISPNAME and EXPTIME are unimportant here
    # AIRMASS must be > 1
    sobjs.write_to_fits(
        {
            'PYP_SPEC': 'p200_dbsp_blue',
            'DISPNAME': '600/4000',
            'EXPTIME': 1.0,
            'AIRMASS': 1.1
        }, spec1d_file)

    par = pypeitpar.PypeItPar()

    # set the senfunc algorithm
    par['sensfunc']['algorithm'] = algorithm
    # needed to initiate SensFunc (dummy standard star Feige34)
    par['sensfunc']['star_ra'] = 159.9042
    par['sensfunc']['star_dec'] = 43.1025

    sensobj = sensfunc.SensFunc.get_instance(spec1d_file, sens_file,
                                             par['sensfunc'])

    sensobj.wave = np.linspace(3000, 6000, 300).reshape((300, 1))
    sensobj.sens = sensobj.empty_sensfunc_table(*sensobj.wave.T.shape)
    # make the zeropoint such that the sensfunc is flat
    sensobj.zeropoint = 30 - np.log10(sensobj.wave**2) / 0.4

    sensobj.to_file(sens_file)

    # now flux our N_lam = 1 specobj
    par['fluxcalib']['extinct_correct'] = None
    fluxCalibrate = fluxcalibrate.MultiSlitFC([spec1d_file], [sens_file],
                                              par=par['fluxcalib'])
    # without extinction correction, we should get constant F_lam
    # with extinction correction, the spectrum will be blue

    # make sure that the appropriate default behavior occurred
    sobjs = specobjs.SpecObjs.from_fitsfile(spec1d_file)
    print(sobjs[0].keys())
    if algorithm == 'UVIS':
        assert sobjs[0]['OPT_FLAM'][0] > sobjs[0]['OPT_FLAM'][-1], \
            "UVIS sensfunc was not extinction corrected by default, but should have been"
    elif algorithm == 'IR':
        assert np.isclose(sobjs[0]['OPT_FLAM'][0], sobjs[0]['OPT_FLAM'][-1]), \
            "IR sensfunc was extinction corrected by default, but shouldn't have been"

    # clean up
    os.remove(spec1d_file)
    os.remove(sens_file)
Exemplo n.º 20
0
def load_specobjs(fname, order=None):
    """ Load a spec1d file into a list of SpecObjExp objects
    Parameters
    ----------
    fname : str

    Returns
    -------
    specObjs : list of SpecObjExp
    head0
    """
    sobjs = specobjs.SpecObjs()
    speckeys = [
        'WAVE', 'WAVE_GRID_MASK', 'WAVE_GRID', 'WAVE_GRID_MIN',
        'WAVE_GRID_MAX', 'SKY', 'MASK', 'FLAM', 'FLAM_IVAR', 'FLAM_SIG',
        'COUNTS_IVAR', 'COUNTS', 'COUNTS_SIG'
    ]
    # sobjs_keys gives correspondence between header cards and sobjs attribute name
    sobjs_key = specobjs.SpecObj.sobjs_key()
    hdulist = fits.open(fname)
    head0 = hdulist[0].header
    #pypeline = head0['PYPELINE']
    # Is this an Echelle reduction?
    #if 'Echelle' in pypeline:
    #    echelle = True
    #else:
    #    echelle = False

    for hdu in hdulist:
        if hdu.name == 'PRIMARY':
            continue
        # Parse name
        idx = hdu.name
        objp = idx.split('-')
        if objp[-2][:5] == 'ORDER':
            iord = int(objp[-2][5:])
        else:
            msgs.warn('Loading longslit data ?')
            iord = int(-1)
        if (order is not None) and (iord != order):
            continue
        specobj = specobjs.SpecObj(None, None, None, idx=idx)
        # Assign specobj attributes from header cards
        for attr, hdrcard in sobjs_key.items():
            try:
                value = hdu.header[hdrcard]
            except:
                continue
            setattr(specobj, attr, value)
        # Load data
        spec = Table(hdu.data)
        shape = (len(spec), 1024)  # 2nd number is dummy
        specobj.shape = shape
        specobj.trace_spat = spec['TRACE']
        # Add spectrum
        if 'BOX_COUNTS' in spec.keys():
            for skey in speckeys:
                try:
                    specobj.boxcar[skey] = spec['BOX_{:s}'.format(skey)].data
                except KeyError:
                    pass
            # Add units on wave
            specobj.boxcar['WAVE'] = specobj.boxcar['WAVE'] * units.AA

        if 'OPT_COUNTS' in spec.keys():
            for skey in speckeys:
                try:
                    specobj.optimal[skey] = spec['OPT_{:s}'.format(skey)].data
                except KeyError:
                    pass
            # Add units on wave
            specobj.optimal['WAVE'] = specobj.optimal['WAVE'] * units.AA
        # Append
        sobjs.add_sobj(specobj)

    # Return
    return sobjs, head0
Exemplo n.º 21
0
    def find_objects_pypeline(self, image, ivar, std=False, std_trace = None, maskslits=None,
                              manual_extract_dict=None,
                              show_peaks=False, show_fits=False, show_trace=False, show=False, debug=False):
        """
        Find objects in the slits. This is currently setup only for ARMS

        Wrapper to extract.objfind

        Parameters
        ----------
        tslits_dict: dict
           Dictionary containing information on the slits traced for this image

        Optional Parameters
        -------------------
        SHOW_PEAKS:  bool
          Generate QA showing peaks identified by object finding

        SHOW_FITS:  bool
          Generate QA  showing fits to traces

        SHOW_TRACE:  bool
          Generate QA  showing traces identified. Requires an open ginga RC modules window

        Returns
        -------
        specobjs : Specobjs object
            Container holding Specobj objects
        nobj:
            Number of objects identified
        self.skymask : ndarray
                Boolean image indicating which pixels are useful for global sky subtraction

        """

        self.maskslits = self.maskslits if maskslits is None else maskslits
        gdslits = np.where(np.invert(self.maskslits))[0]

        # create the ouptut image for skymask
        skymask = np.zeros_like(image, dtype=bool)
        # Instantiate the specobjs container
        sobjs = specobjs.SpecObjs()

        # Loop on slits
        for slit in gdslits:
            qa_title ="Finding objects on slit # {:d}".format(slit)
            msgs.info(qa_title)
            thismask = (self.slitmask == slit)
            inmask = (self.mask == 0) & thismask
            # Find objects
            specobj_dict = {'setup': self.setup, 'slitid': slit, 'orderindx': 999,
                            'det': self.det, 'objtype': self.objtype, 'pypeline': self.pypeline}

            # TODO we need to add QA paths and QA hooks. QA should be
            # done through objfind where all the relevant information
            # is. This will be a png file(s) per slit.

            sig_thresh = 30.0 if std else self.redux_par['sig_thresh']
            #
            sobjs_slit, skymask[thismask] = \
                extract.objfind(image, thismask, self.tslits_dict['slit_left'][:,slit],self.tslits_dict['slit_righ'][:,slit],
                inmask=inmask, ncoeff=self.redux_par['trace_npoly'],
                std_trace=std_trace, sig_thresh=sig_thresh, hand_extract_dict=manual_extract_dict, #self.redux_par['manual'],
                specobj_dict=specobj_dict, show_peaks=show_peaks,show_fits=show_fits, show_trace=show_trace,
                qa_title=qa_title, nperslit=self.redux_par['maxnumber'])
            sobjs.add_sobj(sobjs_slit)

        # Steps
        self.steps.append(inspect.stack()[0][3])
        if show:
            self.show('image', image=image*(self.mask == 0), chname = 'objfind',
                      sobjs=sobjs, slits=True)

        # Return
        return sobjs, len(sobjs), skymask
def ech_objfind(image,
                ivar,
                ordermask,
                slit_left,
                slit_righ,
                inmask=None,
                plate_scale=0.2,
                npca=2,
                ncoeff=5,
                min_snr=0.0,
                nabove_min_snr=0,
                pca_percentile=20.0,
                snr_pca=3.0,
                box_radius=2.0,
                show_peaks=False,
                show_fits=False,
                show_trace=False):

    if inmask is None:
        inmask = (ordermask > 0)

    frameshape = image.shape
    nspec = frameshape[0]
    norders = slit_left.shape[1]

    if isinstance(plate_scale, (float, int)):
        plate_scale_ord = np.full(
            norders, plate_scale)  # 0.12 binned by 3 spatially for HIRES
    elif isinstance(plate_scale, (np.ndarray, list, tuple)):
        if len(plate_scale) == norders:
            plate_scale_ord = plate_scale
        elif len(plate_scale) == 1:
            plate_scale_ord = np.full(norders, plate_scale[0])
        else:
            msgs.error(
                'Invalid size for plate_scale. It must either have one element or norders elements'
            )
    else:
        msgs.error('Invalid type for plate scale')

    specmid = nspec // 2
    slit_width = slit_righ - slit_left
    spec_vec = np.arange(nspec)
    slit_spec_pos = nspec / 2.0
    slit_spat_pos = np.zeros((norders, 2))
    for iord in range(norders):
        slit_spat_pos[iord, :] = (np.interp(slit_spec_pos, spec_vec,
                                            slit_left[:, iord]),
                                  np.interp(slit_spec_pos, spec_vec,
                                            slit_righ[:, iord]))

    # Loop over orders and find objects
    sobjs = specobjs.SpecObjs()
    show_peaks = True
    show_fits = True
    # ToDo replace orderindx with the true order number here? Maybe not. Clean up slitid and orderindx!
    for iord in range(norders):
        msgs.info('Finding objects on slit # {:d}'.format(iord + 1))
        thismask = ordermask == (iord + 1)
        inmask_iord = inmask & thismask
        specobj_dict = {
            'setup': 'HIRES',
            'slitid': iord + 1,
            'scidx': 0,
            'det': 1,
            'objtype': 'science'
        }
        sobjs_slit, skymask[thismask], objmask[thismask], proc_list = \
            extract.objfind(image, thismask, slit_left[:,iord], slit_righ[:,iord], inmask=inmask_iord,show_peaks=show_peaks,
                            show_fits=show_fits, show_trace=False, specobj_dict = specobj_dict)#, sig_thresh = 3.0)
        # ToDO make the specobjs _set_item_ work with expressions like this spec[:].orderindx = iord
        for spec in sobjs_slit:
            spec.ech_orderindx = iord
        sobjs.add_sobj(sobjs_slit)

    nfound = len(sobjs)

    # Compute the FOF linking length based on the instrument place scale and matching length FOFSEP = 1.0"
    FOFSEP = 1.0  # separation of FOF algorithm in arcseconds
    FOF_frac = FOFSEP / (np.median(slit_width) * np.median(plate_scale_ord))

    # Feige: made the code also works for only one object found in one order
    # Run the FOF. We use fake coordinaes
    fracpos = sobjs.spat_fracpos
    ra_fake = fracpos / 1000.0  # Divide all angles by 1000 to make geometry euclidian
    dec_fake = 0.0 * fracpos
    if nfound > 1:
        (ingroup, multgroup, firstgroup,
         nextgroup) = spheregroup(ra_fake, dec_fake, FOF_frac / 1000.0)
        group = ingroup.copy()
        uni_group, uni_ind = np.unique(group, return_index=True)
        nobj = len(uni_group)
        msgs.info('FOF matching found {:d}'.format(nobj) + ' unique objects')
    elif nfound == 1:
        group = np.zeros(1, dtype='int')
        uni_group, uni_ind = np.unique(group, return_index=True)
        nobj = len(group)
        msgs.warn('Only find one object no FOF matching is needed')

    gfrac = np.zeros(nfound)
    for jj in range(nobj):
        this_group = group == uni_group[jj]
        gfrac[this_group] = np.median(fracpos[this_group])

    uni_frac = gfrac[uni_ind]

    sobjs_align = sobjs.copy()
    # Now fill in the missing objects and their traces
    for iobj in range(nobj):
        for iord in range(norders):
            # Is there an object on this order that grouped into the current group in question?
            on_slit = (group == uni_group[iobj]) & (sobjs_align.ech_orderindx
                                                    == iord)
            if not np.any(on_slit):
                # Add this to the sobjs_align, and assign required tags
                thisobj = specobjs.SpecObj(frameshape,
                                           slit_spat_pos[iord, :],
                                           slit_spec_pos,
                                           det=sobjs_align[0].det,
                                           setup=sobjs_align[0].setup,
                                           slitid=(iord + 1),
                                           scidx=sobjs_align[0].scidx,
                                           objtype=sobjs_align[0].objtype)
                thisobj.ech_orderindx = iord
                thisobj.spat_fracpos = uni_frac[iobj]
                thisobj.trace_spat = slit_left[:,
                                               iord] + slit_width[:, iord] * uni_frac[
                                                   iobj]  # new trace
                thisobj.trace_spec = spec_vec
                thisobj.spat_pixpos = thisobj.trace_spat[specmid]
                thisobj.set_idx()
                # Use the real detections of this objects for the FWHM
                this_group = group == uni_group[iobj]
                # Assign to the fwhm of the nearest detected order
                imin = np.argmin(
                    np.abs(sobjs_align[this_group].ech_orderindx - iord))
                thisobj.fwhm = sobjs_align[imin].fwhm
                thisobj.maskwidth = sobjs_align[imin].maskwidth
                thisobj.ech_fracpos = uni_frac[iobj]
                thisobj.ech_group = uni_group[iobj]
                thisobj.ech_usepca = True
                sobjs_align.add_sobj(thisobj)
                group = np.append(group, uni_group[iobj])
                gfrac = np.append(gfrac, uni_frac[iobj])
            else:
                # ToDo fix specobjs to get rid of these crappy loops!
                for spec in sobjs_align[on_slit]:
                    spec.ech_fracpos = uni_frac[iobj]
                    spec.ech_group = uni_group[iobj]
                    spec.ech_usepca = False

    # Some code to ensure that the objects are sorted in the sobjs_align by fractional position on the order and by order
    # respectively
    sobjs_sort = specobjs.SpecObjs()
    for iobj in range(nobj):
        this_group = group == uni_group[iobj]
        this_sobj = sobjs_align[this_group]
        sobjs_sort.add_sobj(this_sobj[np.argsort(this_sobj.ech_orderindx)])

    # Loop over the objects and perform a quick and dirty extraction to assess S/N.
    varimg = utils.calc_ivar(ivar)
    flux_box = np.zeros((nspec, norders, nobj))
    ivar_box = np.zeros((nspec, norders, nobj))
    mask_box = np.zeros((nspec, norders, nobj))
    SNR_arr = np.zeros((norders, nobj))
    for iobj in range(nobj):
        for iord in range(norders):
            indx = (sobjs_sort.ech_group
                    == uni_group[iobj]) & (sobjs_sort.ech_orderindx == iord)
            spec = sobjs_sort[indx]
            thismask = ordermask == (iord + 1)
            inmask_iord = inmask & thismask
            box_rad_pix = box_radius / plate_scale_ord[iord]
            flux_tmp = extract.extract_boxcar(image * inmask_iord,
                                              spec.trace_spat,
                                              box_rad_pix,
                                              ycen=spec.trace_spec)
            var_tmp = extract.extract_boxcar(varimg * inmask_iord,
                                             spec.trace_spat,
                                             box_rad_pix,
                                             ycen=spec.trace_spec)
            ivar_tmp = utils.calc_ivar(var_tmp)
            pixtot = extract.extract_boxcar(ivar * 0 + 1.0,
                                            spec.trace_spat,
                                            box_rad_pix,
                                            ycen=spec.trace_spec)
            mask_tmp = (extract.extract_boxcar(ivar * inmask_iord == 0.0,
                                               spec.trace_spat,
                                               box_rad_pix,
                                               ycen=spec.trace_spec) != pixtot)
            flux_box[:, iord, iobj] = flux_tmp * mask_tmp
            ivar_box[:, iord, iobj] = np.fmax(ivar_tmp * mask_tmp, 0.0)
            mask_box[:, iord, iobj] = mask_tmp
            (mean, med_sn, stddev) = sigma_clipped_stats(
                flux_box[mask_tmp, iord, iobj] *
                np.sqrt(ivar_box[mask_tmp, iord, iobj]),
                sigma_lower=5.0,
                sigma_upper=5.0)
            SNR_arr[iord, iobj] = med_sn

    # Purge objects with low SNR and that don't show up in enough orders
    keep_obj = np.zeros(nobj, dtype=bool)
    sobjs_trim = specobjs.SpecObjs()
    uni_group_trim = np.array([], dtype=int)
    uni_frac_trim = np.array([], dtype=float)
    for iobj in range(nobj):
        if (np.sum(SNR_arr[:, iobj] > min_snr) >= nabove_min_snr):
            keep_obj[iobj] = True
            ikeep = sobjs_sort.ech_group == uni_group[iobj]
            sobjs_trim.add_sobj(sobjs_sort[ikeep])
            uni_group_trim = np.append(uni_group_trim, uni_group[iobj])
            uni_frac_trim = np.append(uni_frac_trim, uni_frac[iobj])
        else:
            msgs.info(
                'Purging object #{:d}'.format(iobj) +
                ' which does not satisfy min_snr > {:5.2f}'.format(min_snr) +
                ' on at least nabove_min_snr >= {:d}'.format(nabove_min_snr) +
                ' orders')

    nobj_trim = np.sum(keep_obj)
    if nobj_trim == 0:
        return specobjs.SpecObjs()

    SNR_arr_trim = SNR_arr[:, keep_obj]

    # Do a final loop over objects and make the final decision about which orders will be interpolated/extrapolated by the PCA
    for iobj in range(nobj_trim):
        SNR_now = SNR_arr_trim[:, iobj]
        indx = (sobjs_trim.ech_group == uni_group_trim[iobj])
        # PCA interp/extrap if:
        #      (SNR is below pca_percentile of the total SNRs) AND (SNR < snr_pca)
        #                                 OR
        #      (if this order was not originally traced by the object finding, see above)
        usepca = ((SNR_now < np.percentile(SNR_now, pca_percentile)) &
                  (SNR_now < snr_pca)) | sobjs_trim[indx].ech_usepca
        # ToDo fix specobjs to get rid of these crappy loops!
        for iord, spec in enumerate(sobjs_trim[indx]):
            spec.ech_usepca = usepca[iord]
            if usepca[iord]:
                msgs.info('Using PCA to predict trace for object #{:d}'.format(
                    iobj) + ' on order #{:d}'.format(iord))

    sobjs_final = sobjs_trim.copy()
    # Loop over the objects one by one and adjust/predict the traces
    npoly_cen = 3
    pca_fits = np.zeros((nspec, norders, nobj_trim))
    for iobj in range(nobj_trim):
        igroup = sobjs_final.ech_group == uni_group_trim[iobj]
        # PCA predict the masked orders which were not traced
        pca_fits[:, :, iobj] = pca_trace((sobjs_final[igroup].trace_spat).T,
                                         usepca=None,
                                         npca=npca,
                                         npoly_cen=npoly_cen)
        # usepca = sobjs_final[igroup].ech_usepca,
        # Perform iterative flux weighted centroiding using new PCA predictions
        xinit_fweight = pca_fits[:, :, iobj].copy()
        inmask_now = inmask & (ordermask > 0)
        xfit_fweight = extract.iter_tracefit(image,
                                             xinit_fweight,
                                             ncoeff,
                                             inmask=inmask_now,
                                             show_fits=show_fits)
        # Perform iterative Gaussian weighted centroiding
        xinit_gweight = xfit_fweight.copy()
        xfit_gweight = extract.iter_tracefit(image,
                                             xinit_gweight,
                                             ncoeff,
                                             inmask=inmask_now,
                                             gweight=True,
                                             show_fits=show_fits)
        # Assign the new traces
        for iord, spec in enumerate(sobjs_final[igroup]):
            spec.trace_spat = xfit_gweight[:, iord]
            spec.spat_pixpos = spec.trace_spat[specmid]

    # Set the IDs
    sobjs_final.set_idx()
    if show_trace:
        viewer, ch = ginga.show_image(objminsky * (ordermask > 0))
        for iobj in range(nobj_trim):
            for iord in range(norders):
                ginga.show_trace(viewer,
                                 ch,
                                 pca_fits[:, iord, iobj],
                                 str(uni_frac[iobj]),
                                 color='yellow')

        for spec in sobjs_trim:
            color = 'green' if spec.ech_usepca else 'magenta'
            ginga.show_trace(viewer,
                             ch,
                             spec.trace_spat,
                             spec.idx,
                             color=color)

        #for spec in sobjs_final:
        #    color = 'red' if spec.ech_usepca else 'green'
        #    ginga.show_trace(viewer, ch, spec.trace_spat, spec.idx, color=color)

    return sobjs_final
Exemplo n.º 23
0
def main(args):
    """ Executes 2d coadding
    """
    msgs.warn('PATH =' + os.getcwd())
    # Load the file
    if args.file is not None:
        spectrograph_name, config_lines, spec2d_files = io.read_spec2d_file(
            args.file, filetype="coadd2d")
        spectrograph = load_spectrograph(spectrograph_name)

        # Parameters
        # TODO: Shouldn't this reinstantiate the same parameters used in
        # the PypeIt run that extracted the objects?  Why are we not
        # just passing the pypeit file?
        # JFH: The reason is that the coadd2dfile may want different reduction parameters
        spectrograph_def_par = spectrograph.default_pypeit_par()
        parset = par.PypeItPar.from_cfg_lines(
            cfg_lines=spectrograph_def_par.to_config(),
            merge_with=config_lines)
    elif args.obj is not None:
        # TODO: We should probably be reading the pypeit file and using those parameters here rather than using the
        # default parset.
        # TODO: This needs to define the science path
        spec2d_files = glob.glob('./Science/spec2d_*' + args.obj + '*')
        head0 = fits.getheader(spec2d_files[0])
        spectrograph_name = head0['PYP_SPEC']
        spectrograph = load_spectrograph(spectrograph_name)
        parset = spectrograph.default_pypeit_par()
    else:
        msgs.error(
            'You must either input a coadd2d file with --file or an object name with --obj'
        )

    # Update with configuration specific parameters (which requires science file) and initialize spectrograph
    spectrograph_cfg_lines = spectrograph.config_specific_par(
        spec2d_files[0]).to_config()
    parset = par.PypeItPar.from_cfg_lines(cfg_lines=spectrograph_cfg_lines,
                                          merge_with=parset.to_config())

    # If detector was passed as an argument override whatever was in the coadd2d_file
    if args.det is not None:
        msgs.info("Restricting reductions to detector={}".format(args.det))
        parset['rdx']['detnum'] = int(args.det)

    # Get headers (if possible) and base names
    spec1d_files = [
        files.replace('spec2d', 'spec1d') for files in spec2d_files
    ]
    head1d = None
    for spec1d_file in spec1d_files:
        if os.path.isfile(spec1d_file):
            head1d = fits.getheader(spec1d_file)
            break
    if head1d is None:
        msgs.warn("No 1D spectra so am generating a dummy header for output")
        head1d = io.initialize_header()

    head2d = fits.getheader(spec2d_files[0])
    if args.basename is None:
        filename = os.path.basename(spec2d_files[0])
        basename = filename.split('_')[2]
    else:
        basename = args.basename

    # Write the par to disk
    par_outfile = basename + '_coadd2d.par'
    print("Writing the parameters to {}".format(par_outfile))
    parset.to_config(par_outfile)

    # Now run the coadds

    skysub_mode = head2d['SKYSUB']
    ir_redux = True if 'DIFF' in skysub_mode else False

    # Print status message
    msgs_string = 'Reducing target {:s}'.format(basename) + msgs.newline()
    msgs_string += 'Performing coadd of frames reduce with {:s} imaging'.format(
        skysub_mode)
    msgs_string += msgs.newline(
    ) + 'Combining frames in 2d coadd:' + msgs.newline()
    for file in spec2d_files:
        msgs_string += '{0:s}'.format(os.path.basename(file)) + msgs.newline()
    msgs.info(msgs_string)

    # TODO: This needs to be added to the parameter list for rdx
    redux_path = os.getcwd()
    master_dirname = os.path.basename(head2d['PYPMFDIR']) + '_coadd'
    master_dir = os.path.join(redux_path, master_dirname)

    # Make the new Master dir
    if not os.path.isdir(master_dir):
        msgs.info(
            'Creating directory for Master output: {0}'.format(master_dir))
        os.makedirs(master_dir)

    # Instantiate the sci_dict
    sci_dict = OrderedDict()  # This needs to be ordered
    sci_dict['meta'] = {}
    sci_dict['meta']['vel_corr'] = 0.
    sci_dict['meta']['ir_redux'] = ir_redux

    # Find the detectors to reduce
    detectors = PypeIt.select_detectors(detnum=parset['rdx']['detnum'],
                                        ndet=spectrograph.ndet)
    if len(detectors) != spectrograph.ndet:
        msgs.warn('Not reducing detectors: {0}'.format(' '.join([
            str(d)
            for d in set(np.arange(spectrograph.ndet) + 1) - set(detectors)
        ])))

    # Loop on detectors
    for det in detectors:
        msgs.info("Working on detector {0}".format(det))
        sci_dict[det] = {}

        # Instantiate Coadd2d
        coadd = coadd2d.CoAdd2D.get_instance(
            spec2d_files,
            spectrograph,
            parset,
            det=det,
            offsets=parset['coadd2d']['offsets'],
            weights=parset['coadd2d']['weights'],
            ir_redux=ir_redux,
            debug_offsets=args.debug_offsets,
            debug=args.debug,
            samp_fact=args.samp_fact)

        # Coadd the slits
        coadd_dict_list = coadd.coadd(
            only_slits=None)  # TODO implement only_slits later
        # Create the pseudo images
        pseudo_dict = coadd.create_pseudo_image(coadd_dict_list)
        # Reduce
        msgs.info('Running the extraction')
        # TODO -- This should mirror what is in pypeit.extract_one
        # TODO -- JFH :: This ought to return a Spec2DObj and SpecObjs which would be slurped into
        #  AllSpec2DObj and all_specobsj, as below.
        # TODO -- JFH -- Check that the slits we are using are correct
        sci_dict[det]['sciimg'], sci_dict[det]['sciivar'], sci_dict[det]['skymodel'], sci_dict[det]['objmodel'], \
        sci_dict[det]['ivarmodel'], sci_dict[det]['outmask'], sci_dict[det]['specobjs'], sci_dict[det]['detector'], \
            sci_dict[det]['slits'], sci_dict[det]['tilts'], sci_dict[det]['waveimg'] = coadd.reduce(
            pseudo_dict, show = args.show, show_peaks = args.peaks)

        # Save pseudo image master files
        #coadd.save_masters()

    # Make the new Science dir
    # TODO: This needs to be defined by the user
    scipath = os.path.join(redux_path, 'Science_coadd')
    if not os.path.isdir(scipath):
        msgs.info('Creating directory for Science output: {0}'.format(scipath))
        os.makedirs(scipath)

    # THE FOLLOWING MIMICS THE CODE IN pypeit.save_exposure()

    # TODO -- These lines should be above once reduce() passes back something sensible
    all_specobjs = specobjs.SpecObjs()
    for det in detectors:
        all_specobjs.add_sobj(sci_dict[det]['specobjs'])

    # Write
    outfile1d = os.path.join(scipath, 'spec1d_{:s}.fits'.format(basename))
    subheader = spectrograph.subheader_for_spec(head2d, head2d)
    all_specobjs.write_to_fits(subheader, outfile1d)

    # 2D spectra
    # TODO -- These lines should be above once reduce() passes back something sensible
    all_spec2d = spec2dobj.AllSpec2DObj()
    all_spec2d['meta']['ir_redux'] = ir_redux
    for det in detectors:
        all_spec2d[det] = spec2dobj.Spec2DObj(
            det=det,
            sciimg=sci_dict[det]['sciimg'],
            ivarraw=sci_dict[det]['sciivar'],
            skymodel=sci_dict[det]['skymodel'],
            objmodel=sci_dict[det]['objmodel'],
            ivarmodel=sci_dict[det]['ivarmodel'],
            scaleimg=np.array([1.0], dtype=np.float),
            bpmmask=sci_dict[det]['outmask'],
            detector=sci_dict[det]['detector'],
            slits=sci_dict[det]['slits'],
            waveimg=sci_dict[det]['waveimg'],
            tilts=sci_dict[det]['tilts'],
            sci_spat_flexure=None,
            sci_spec_flexure=None,
            vel_corr=None,
            vel_type=None)
    # Build header
    outfile2d = os.path.join(scipath, 'spec2d_{:s}.fits'.format(basename))
    pri_hdr = all_spec2d.build_primary_hdr(
        head2d,
        spectrograph,
        subheader=subheader,
        # TODO -- JFH :: Decide if we need any of these
        redux_path=None,
        master_key_dict=None,
        master_dir=None)
    # Write
    all_spec2d.write_to_fits(outfile2d, pri_hdr=pri_hdr)
Exemplo n.º 24
0
def save_all(sci_dict,
             master_key_dict,
             master_dir,
             spectrograph,
             head1d,
             head2d,
             scipath,
             basename,
             update_det=None,
             binning='None'):
    """
    Routine to save PypeIt 1d and 2d outputs

    Args:
        sci_dict: dict
            Dictionary containing extraction outputs
        master_key_dict: dict
            Dictionary with master key information for this reduction
        master_dir: str
            Directory where the master files live
        spectrograph: object, spectrograph
            Spectrograph object for the spectorgraph that was used
        head1d: dict
            fitstbl meta data dictionary that will become the header for the spec1d files
        head2d: dict
            rawfile header that will become the header for the spec2d files
        scipath: str
            path to which the outputs should be written
        basename: str
            the object basename
        refframe: str, default = 'heliocentric'
            Reference frame for the wavelengths
        update_det : int or list, default=None
            If provided, do not clobber the existing file but only update
            the indicated detectors.  Useful for re-running on a subset of detectors

    Returns:

    """
    # Check for the directory
    if not os.path.isdir(scipath):
        os.makedirs(scipath)

    # Filenames to write out
    # TODO: These should be centrally defined so that they don't become
    # out of sync with what's in pypeit.PypeIt
    outfile1d = os.path.join(scipath, 'spec1d_{:s}.fits'.format(basename))
    outfile2d = os.path.join(scipath, 'spec2d_{:s}.fits'.format(basename))
    outfiletxt = os.path.join(scipath, 'spec1d_{:s}.txt'.format(basename))

    # TODO: Need some checks here that the exposure has been reduced

    # Build the final list of specobjs and vel_corr
    all_specobjs = specobjs.SpecObjs()

    for key in sci_dict:
        try:
            all_specobjs.add_sobj(sci_dict[key]['specobjs'])
        except KeyError:  # No object extracted
            continue

    if len(all_specobjs) == 0:
        msgs.warn('No objects to save. Only writing spec2d files!')
    else:
        # Build the spec1d output header.
        header = all_specobjs.build_header(head1d, head2d, spectrograph)
        all_specobjs.write_to_fits(header, outfile1d, update_det=update_det)
        # Txt file
        # TODO JFH: Make this a method in the specobjs class.
        save_obj_info(all_specobjs, spectrograph, outfiletxt, binning=binning)

    # Write 2D images for the Science Frame
    save_2d_images(sci_dict,
                   head2d,
                   spectrograph,
                   master_key_dict,
                   master_dir,
                   outfile2d,
                   update_det=update_det)

    return
Exemplo n.º 25
0
    def main(args):
        """ Executes 2d coadding
        """
        msgs.info('PATH =' + os.getcwd())
        # Load the file
        if args.file is not None:
            spectrograph_name, config_lines, spec2d_files \
                    = io.read_spec2d_file(args.file, filetype="coadd2d")
            spectrograph = load_spectrograph(spectrograph_name)

            # Parameters
            # TODO: Shouldn't this reinstantiate the same parameters used in
            # the PypeIt run that extracted the objects?  Why are we not
            # just passing the pypeit file?
            # JFH: The reason is that the coadd2dfile may want different reduction parameters
            # DP: I think config_specific_par() is more appropriate here. default_pypeit_par()
            # is included in config_specific_par()
            # NOTE `config_specific_par` works with the spec2d files because we construct the header
            # of those files to include all the relevant keywords from the raw file.
            spectrograph_cfg_lines = spectrograph.config_specific_par(
                spec2d_files[0]).to_config()
            parset = par.PypeItPar.from_cfg_lines(
                cfg_lines=spectrograph_cfg_lines, merge_with=config_lines)

        elif args.obj is not None:
            # TODO: We should probably be reading the pypeit file and using
            # those parameters here rather than using the default parset.

            # TODO: This needs to define the science path
            spec2d_files = glob.glob('./Science/spec2d_*' + args.obj + '*')
            head0 = fits.getheader(spec2d_files[0])
            spectrograph_name = head0['PYP_SPEC']
            spectrograph = load_spectrograph(spectrograph_name)
            # NOTE `config_specific_par` works with the spec2d files because we construct the header
            # of those files to include all the relevant keywords from the raw file.
            spectrograph_cfg_lines = spectrograph.config_specific_par(
                spec2d_files[0]).to_config()
            parset = par.PypeItPar.from_cfg_lines(
                cfg_lines=spectrograph_cfg_lines)
        else:
            return msgs.error(
                'You must provide either a coadd2d file (--file) or an object name (--obj)'
            )

        # If detector was passed as an argument override whatever was in the coadd2d_file
        if args.det is not None:
            msgs.info("Restricting reductions to detector={}".format(args.det))
            # parset['rdx']['detnum'] = par.util.eval_tuple(args.det.split(','))
            # TODO this needs to be adjusted if we want to pass (as inline command) mosaic detectors
            parset['rdx']['detnum'] = [int(d) for d in args.det.split(',')]

        # Get headers (if possible) and base names
        spec1d_files = [
            files.replace('spec2d', 'spec1d') for files in spec2d_files
        ]
        head1d = None
        for spec1d_file in spec1d_files:
            if os.path.isfile(spec1d_file):
                head1d = fits.getheader(spec1d_file)
                break
        if head1d is None:
            msgs.warn(
                "No 1D spectra so am generating a dummy header for output")
            head1d = io.initialize_header()

        head2d = fits.getheader(spec2d_files[0])
        if args.basename is None:
            #TODO Fix this, currently does not work if target names have - or _
            filename_first = os.path.basename(spec2d_files[0])
            filename_last = os.path.basename(spec2d_files[-1])
            prefix_first = (filename_first.split('_')[1]).split('-')[0]
            prefix_last = (filename_last.split('_')[1]).split('-')[0]
            objname = (filename_first.split('-')[1]).split('_')[0]
            basename = '{:s}-{:s}-{:s}'.format(prefix_first, prefix_last,
                                               objname)
        else:
            basename = args.basename

        # TODO Heliocentric for coadd2d needs to be thought through. Currently turning it off.
        parset['calibrations']['wavelengths']['refframe'] = 'observed'
        # TODO Flexure correction for coadd2d needs to be thought through. Currently turning it off.
        parset['flexure']['spec_method'] = 'skip'
        # Write the par to disk
        par_outfile = basename + '_coadd2d.par'
        print("Writing the parameters to {}".format(par_outfile))
        parset.to_config(par_outfile,
                         exclude_defaults=True,
                         include_descr=False)

        # Now run the coadds

        skysub_mode = head2d['SKYSUB']
        findobj_mode = head2d['FINDOBJ']
        bkg_redux = True if 'DIFF' in skysub_mode else False
        find_negative = True if 'NEG' in findobj_mode else False

        # Print status message
        msgs_string = 'Reducing target {:s}'.format(basename) + msgs.newline()
        msgs_string += 'Coadding frame sky-subtraced with {:s}'.format(
            skysub_mode)
        msgs_string += 'Searching for objects that are {:s}'.format(
            findobj_mode)
        msgs_string += msgs.newline(
        ) + 'Combining frames in 2d coadd:' + msgs.newline()
        for f, file in enumerate(spec2d_files):
            msgs_string += 'Exp {0}: {1:s}'.format(
                f, os.path.basename(file)) + msgs.newline()
        msgs.info(msgs_string)

        # TODO: This needs to be added to the parameter list for rdx
        redux_path = os.getcwd()
        master_dirname = os.path.basename(head2d['PYPMFDIR']) + '_coadd'
        master_dir = os.path.join(redux_path, master_dirname)

        # Make the new Master dir
        if not os.path.isdir(master_dir):
            msgs.info(
                'Creating directory for Master output: {0}'.format(master_dir))
            os.makedirs(master_dir)

        # Instantiate the sci_dict
        sci_dict = OrderedDict()  # This needs to be ordered
        sci_dict['meta'] = {}
        sci_dict['meta']['vel_corr'] = 0.
        sci_dict['meta']['bkg_redux'] = bkg_redux
        sci_dict['meta']['find_negative'] = find_negative

        # Make QA coadd directory
        parset['rdx']['qadir'] += '_coadd'
        qa_path = os.path.join(parset['rdx']['redux_path'],
                               parset['rdx']['qadir'], 'PNGs')
        if not os.path.isdir(qa_path):
            os.makedirs(qa_path)

        # Find the detectors to reduce
#        detectors = PypeIt.select_detectors(detnum=parset['rdx']['detnum'], ndet=spectrograph.ndet)
        detectors = spectrograph.select_detectors(
            subset=parset['rdx']['detnum'])
        #        if len(detectors) != spectrograph.ndet:
        #            msgs.warn('Not reducing detectors: {0}'.format(' '.join([str(d) for d in
        #            set(np.arange(spectrograph.ndet) + 1) - set(detectors)])))
        msgs.info(f'Detectors to work on: {detectors}')

        # Only_slits?
        if args.only_slits:
            parset['coadd2d']['only_slits'] = [
                int(item) for item in args.only_slits.split(',')
            ]

        # container for specobjs
        all_specobjs = specobjs.SpecObjs()
        # container for spec2dobj
        all_spec2d = spec2dobj.AllSpec2DObj()
        # set some meta
        all_spec2d['meta']['bkg_redux'] = bkg_redux
        all_spec2d['meta']['find_negative'] = find_negative

        # Loop on detectors
        for det in detectors:
            msgs.info("Working on detector {0}".format(det))

            # Instantiate Coadd2d
            coadd = coadd2d.CoAdd2D.get_instance(
                spec2d_files,
                spectrograph,
                parset,
                det=det,
                offsets=parset['coadd2d']['offsets'],
                weights=parset['coadd2d']['weights'],
                spec_samp_fact=args.spec_samp_fact,
                spat_samp_fact=args.spat_samp_fact,
                bkg_redux=bkg_redux,
                find_negative=find_negative,
                debug_offsets=args.debug_offsets,
                debug=args.debug)

            # TODO Add this stuff to a run method in coadd2d
            # Coadd the slits
            coadd_dict_list = coadd.coadd(
                only_slits=parset['coadd2d']['only_slits'])
            # Create the pseudo images
            pseudo_dict = coadd.create_pseudo_image(coadd_dict_list)
            # Reduce
            msgs.info('Running the extraction')

            # TODO -- This should mirror what is in pypeit.extract_one

            # TODO -- JFH :: This ought to return a Spec2DObj and SpecObjs which
            # would be slurped into AllSpec2DObj and all_specobsj, as below.

            # TODO -- JFH -- Check that the slits we are using are correct

            sci_dict[coadd.detname] = {}
            sci_dict[coadd.detname]['sciimg'], sci_dict[coadd.detname]['sciivar'], \
                sci_dict[coadd.detname]['skymodel'], sci_dict[coadd.detname]['objmodel'], \
                sci_dict[coadd.detname]['ivarmodel'], sci_dict[coadd.detname]['outmask'], \
                sci_dict[coadd.detname]['specobjs'], sci_dict[coadd.detname]['detector'], \
                sci_dict[coadd.detname]['slits'], sci_dict[coadd.detname]['tilts'], \
                sci_dict[coadd.detname]['waveimg'] \
                    = coadd.reduce(pseudo_dict, show=args.show, show_peaks=args.peaks, basename=basename)

            # Tack on detector (similarly to pypeit.extract_one)
            for sobj in sci_dict[coadd.detname]['specobjs']:
                sobj.DETECTOR = sci_dict[coadd.detname]['detector']

            # fill the specobjs container
            all_specobjs.add_sobj(sci_dict[coadd.detname]['specobjs'])

            # fill the spec2dobj container but first ...
            # pull out maskdef_designtab from sci_dict[det]['slits']
            maskdef_designtab = sci_dict[
                coadd.detname]['slits'].maskdef_designtab
            slits = copy.deepcopy(sci_dict[coadd.detname]['slits'])
            slits.maskdef_designtab = None
            # fill up
            all_spec2d[coadd.detname] = spec2dobj.Spec2DObj(
                sciimg=sci_dict[coadd.detname]['sciimg'],
                ivarraw=sci_dict[coadd.detname]['sciivar'],
                skymodel=sci_dict[coadd.detname]['skymodel'],
                objmodel=sci_dict[coadd.detname]['objmodel'],
                ivarmodel=sci_dict[coadd.detname]['ivarmodel'],
                scaleimg=np.array([1.0], dtype=np.float),
                bpmmask=sci_dict[coadd.detname]['outmask'],
                detector=sci_dict[coadd.detname]['detector'],
                slits=slits,
                waveimg=sci_dict[coadd.detname]['waveimg'],
                tilts=sci_dict[coadd.detname]['tilts'],
                sci_spat_flexure=None,
                sci_spec_flexure=None,
                vel_corr=None,
                vel_type=None,
                maskdef_designtab=maskdef_designtab)

            # Save pseudo image master files
            #coadd.save_masters()

        # SAVE TO DISK

        # Make the new Science dir
        # TODO: This needs to be defined by the user
        scipath = os.path.join(redux_path, 'Science_coadd')
        if not os.path.isdir(scipath):
            msgs.info(
                'Creating directory for Science output: {0}'.format(scipath))
            os.makedirs(scipath)

        # THE FOLLOWING MIMICS THE CODE IN pypeit.save_exposure()
        subheader = spectrograph.subheader_for_spec(head2d, head2d)
        # Write spec1D
        if all_specobjs.nobj > 0:
            outfile1d = os.path.join(scipath,
                                     'spec1d_{:s}.fits'.format(basename))
            all_specobjs.write_to_fits(subheader, outfile1d)

            # Info
            outfiletxt = os.path.join(scipath,
                                      'spec1d_{:s}.txt'.format(basename))
            sobjs = specobjs.SpecObjs.from_fitsfile(outfile1d,
                                                    chk_version=False)
            sobjs.write_info(outfiletxt, spectrograph.pypeline)

        # Build header for spec2d
        outfile2d = os.path.join(scipath, 'spec2d_{:s}.fits'.format(basename))
        pri_hdr = all_spec2d.build_primary_hdr(
            head2d,
            spectrograph,
            subheader=subheader,
            # TODO -- JFH :: Decide if we need any of these
            redux_path=None,
            master_key_dict=None,
            master_dir=None)
        # Write spec2d
        all_spec2d.write_to_fits(outfile2d, pri_hdr=pri_hdr)
              (crmask_A == False) & (crmask_B == False)
    ivar_AB = utils.calc_ivar(var_AB) * (mask_AB == True)

    # Sky subtraction
    from pypeit.core import skysub
    from pypeit.core import extract

    slitpix = tslits_dict['slitpix']
    lcen = tslits_dict['lcen']
    rcen = tslits_dict['rcen']
    ximg = tslits_dict['ximg']
    edgmask = tslits_dict['edge_mask']
    FWHM = 5.0
    bsp = 0.8
    residual_img = np.zeros_like(sciframe_A)
    specobjs_pos = specobjs.SpecObjs()
    specobjs_neg = specobjs.SpecObjs()
    skymask = (slitpix > 0)
    for islit in range(1, nslits + 1):
        thismask = (slitpix == islit)
        residual_img[thismask] = skysub.global_skysub(diff_AB,
                                                      ivar_AB,
                                                      mstilts,
                                                      thismask,
                                                      lcen[:,islit-1],
                                                      rcen[:,islit-1],
                                                      inmask=((edgmask == False) & (mask_AB == True)),
                                                      bsp=bsp,
                                                      pos_mask=False,
                                                      show_fit=False)
        image = diff_AB - residual_img