def count_badpixels(self, thresh=10000, prescan=False): amps = common.valid_amps_list() if prescan: return dict([(amp, int(np.sum(self.prescan_cutouts[amp] > thresh))) for amp in amps]) else: return dict([(amp, int(np.sum(self.overscan_cutouts[amp] > thresh))) for amp in amps])
def fit_dark_scaling(im, dark_guess_scaled, extname): # im needs to be bias-subtracted !!!!!!!!! # im should have overscan stripped out # dark should have overscan stripped out # dark should be scaled according to exptime and best guess of # T dependence so that the additional scaling fit out by this routine # is a perturbation sh_im = im.shape assert((sh_im[0] == 1032) and (sh_im[1] == 2048)) sh_dark = dark_guess_scaled.shape assert((sh_dark[0] == 1032) and (sh_dark[1] == 2048)) amps = common.valid_amps_list() rescale_factors = np.zeros(4) ncalls = np.zeros(4, dtype=int) # did optimizer exit successfully or not success = np.zeros(4, dtype=bool) for i, amp in enumerate(amps): res = fit_dark_scaling_1amp(im, dark_guess_scaled, amp, extname) x = res.x rescale_factors[i] = x[0] ncalls[i] = res.nfev success[i] = res.success return rescale_factors, ncalls, success
def __init__(self, image): # image should be a 2D numpy array with dimensions # 2248 x 1032 in the case of DESI GFA cameras par = common.gfa_misc_params() sh = image.shape assert(sh[0] == par['height_with_prescan_overscan']) assert(sh[1] == par['width_with_prescan_overscan']) amps = common.valid_amps_list() self.overscan_cutouts = {} self.prescan_cutouts = {} for amp in amps: bdy = common.overscan_bdy_coords(amp) self.overscan_cutouts[amp] = image[bdy['y_l']:bdy['y_u'], bdy['x_l']:bdy['x_u']] bdy = common.prescan_bdy_coords(amp) self.prescan_cutouts[amp] = image[bdy['y_l']:bdy['y_u'], bdy['x_l']:bdy['x_u']] self.n_badpix_overscan = self.count_badpixels() self.n_badpix_prescan = self.count_badpixels(prescan=True) # still per-amp but summing prescan and overscan counts together self.n_badpix = dict([(amp, self.n_badpix_overscan[amp] + self.n_badpix_prescan[amp]) for amp in amps]) # including all amps and lumping together prescan and overscan self.n_badpix_all = np.sum([n for n in self.n_badpix.values()]) # units are raw ADU self.overscan_medians = dict([(amp, np.median(self.overscan_cutouts[amp])) for amp in amps]) # units are raw ADU self.prescan_medians = dict([(amp, np.median(self.prescan_cutouts[amp])) for amp in amps])
def estimate_sky_mag(self, careful_sky=False, flatfielding_on=True): # calculate sky brightness in mag per sq asec # this is meant to be run on the reduced image in ADU assert(self.are_pixels_calibrated(flatfielding_on=flatfielding_on)) sky_adu_per_pixel = self.estimate_sky_level(careful_sky=careful_sky, flatfielding_on=flatfielding_on) acttime = self.time_s_for_dark sky_mag = sky.adu_to_surface_brightness(sky_adu_per_pixel, acttime, self.extname) if self.sky_level_adu_per_amp is not None: amps = common.valid_amps_list() self.sky_mag_per_amp = [sky.adu_to_surface_brightness(self.sky_level_adu_per_amp[amp], acttime, self.extname) for amp in amps] print(self.extname + ' sky mag per square asec AB : ' + '{:.3f}'.format(sky_mag)) self.sky_mag = sky_mag # calculate sky mag for just upper 8 rows (lumping together amps G and H) self.sky_mag_upper() return sky_mag
def prescan_overscan_ccds_table(tab, exp): # add information about bad pixels in overscan/prescan to # CCDs table, should be useful in identifying reasons for problematic # reductions... tab['npix_bad_total'] = [ exp.images[extname].overscan.n_badpix_all for extname in tab['extname'] ] ampnames = common.valid_amps_list() npix_bad_per_amp = np.zeros((len(tab), len(ampnames)), dtype=int) overscan_medians = np.zeros((len(tab), len(ampnames)), dtype='float32') prescan_medians = np.zeros((len(tab), len(ampnames)), dtype='float32') for i, t in enumerate(tab): npix_bad_per_amp[i, :] = np.array([ exp.images[t['extname']].overscan.n_badpix[amp] for amp in ampnames ]) overscan_medians[i, :] = np.array([ exp.images[t['extname']].overscan.overscan_medians[amp] for amp in ampnames ]) prescan_medians[i, :] = np.array([ exp.images[t['extname']].overscan.prescan_medians[amp] for amp in ampnames ]) tab['npix_bad_per_amp'] = npix_bad_per_amp tab['overscan_medians_adu'] = overscan_medians tab['prescan_medians_adu'] = prescan_medians
def median_zenith_camera_zeropoint(extname): # wraps zenith_zeropoint_photometric_1amp # eventually want to do a better job of dealing with amp-to-amp # zeropoint variation so this is hopefully a temporary hack amps = common.valid_amps_list() zps = [zenith_zeropoint_photometric_1amp(extname, amp) for amp in amps] return np.median(zps)
def subtract_bias(self): print('Attempting to subtract bias...') for extname in self.images.keys(): if self.images[extname] is not None: self.images[extname].image = self.images[extname].image - \ load_calibs.read_bias_image(extname) amps = common.valid_amps_list() for amp in amps: bdy = common.amp_bdy_coords(amp) self.images[extname].image[bdy['y_l']:bdy['y_u'], bdy['x_l']:bdy['x_u']] -= \ self.images[extname].overscan.overscan_medians[amp] self.images[extname].bias_subtracted = True self.images[extname].calc_variance_e_squared()
def estimate_sky_level(self, careful_sky=False, flatfielding_on=True): # do something dumb for now, return to this later with something # more sophisticated, possibly involving segmentation # and/or finding the mode assert(self.are_pixels_calibrated(flatfielding_on=flatfielding_on)) if careful_sky: if self.segmap is None: self.set_segmap() self.sky_level_adu = np.median(self.image[self.segmap.array == 0]) else: self.sky_level_adu = np.median(self.image) self.sky_level_adu_per_amp = {} for amp in common.valid_amps_list(): bdy = common.amp_bdy_coords(amp) self.sky_level_adu_per_amp[amp] = np.median(self.image[bdy['y_l']:bdy['y_u'], bdy['x_l']:bdy['x_u']]) return self.sky_level_adu
def assemble_ccds_table(tab, catalog, exp, outdir, proc_obj, cube_index=None, ps1=None, det_sn_thresh=5.0, sky_mags=True, minimal=False, mjdrange=None): nrows = len(tab) tab['extname'] = tab['camera'] tab['contrast'] = [exp.images[extname].header['CONTRAST'] for extname in tab['camera']] if minimal: return tab if sky_mags: tab['sky_mag_ab'] = [exp.images[extname].sky_mag for extname in tab['camera']] tab['sky_mag_ab_subregion'] = [exp.images[extname].sky_mag_upper for extname in tab['camera']] amps = common.valid_amps_list() if sky_mags: sky_mag_ab_per_amp = np.zeros((nrows, len(amps)), dtype='float32') # 4 amps for i, extname in enumerate(tab['camera']): for j in range(len(amps)): _mag = np.nan if exp.images[extname].sky_mag_per_amp is None else exp.images[extname].sky_mag_per_amp[j] sky_mag_ab_per_amp[i, j] = _mag tab['sky_mag_ab_per_amp'] = sky_mag_ab_per_amp tab['petal_loc'] = np.array([common.gfa_extname_to_gfa_number(extname) for extname in tab['camera']], dtype='uint8') tab['expid'] = [exp.images[extname].header['EXPID'] for extname in tab['camera']] # should work except if early versions of guide cubes lacked # MJD information... tab['mjd'] = [exp.images[extname].try_retrieve_meta_keyword('MJD-OBS', placeholder=0.0) for extname in tab['camera']] eph = util.load_lst() tab['lst_deg'] = [util.interp_ephemeris(t['mjd'], eph=eph) for t in tab] tab['moon_illumination'] = [util.interp_ephemeris(t['mjd'], eph=eph, colname='MPHASE') for t in tab] tab['program'] = [str(exp.images[extname].try_retrieve_meta_keyword('PROGRAM', placeholder='')) for extname in tab['camera']] tab['skyra'] = [exp.images[extname].try_retrieve_meta_keyword('SKYRA', placeholder=np.nan) for extname in tab['camera']] tab['skydec'] = [exp.images[extname].try_retrieve_meta_keyword('SKYDEC', placeholder=np.nan) for extname in tab['camera']] # zenith distance using approximate center of the field given by SKYRA, SKYDEC tab['zenith_dist_deg'] = [util._zenith_distance(t['skyra'], t['skydec'], t['lst_deg']) for t in tab] tab['domshutl'] = np.array(nrows*[exp.try_retrieve_header_card('DOMSHUTL', placeholder='')], dtype='U8') tab['domshutu'] = np.array(nrows*[exp.try_retrieve_header_card('DOMSHUTU', placeholder='')], dtype='U8') tab['pmcover'] = exp.try_retrieve_header_card('PMCOVER', placeholder='') tab['moonra'] = exp.try_retrieve_header_card('MOONRA', placeholder=np.nan) tab['moondec'] = exp.try_retrieve_header_card('MOONDEC', placeholder=np.nan) if np.isnan(tab['moonra'][0]): tab['moonra'] = util.interp_ephemeris(tab['mjd'][0], eph=eph, colname='MOONRA') if np.isnan(tab['moondec'][0]): tab['moondec'] = util.interp_ephemeris(tab['mjd'][0], eph=eph, colname='MOONDEC') tab['moon_zd_deg'] = util._zenith_distance(tab['moonra'][0], tab['moondec'][0], tab['lst_deg'][0]) tab['t_c_for_dark'] = [exp.images[extname].t_c_for_dark for extname in tab['camera']] tab['t_c_for_dark_is_guess'] = [int(exp.images[extname].t_c_for_dark_is_guess) for extname in tab['camera']] tab['time_s_for_dark'] = [exp.images[extname].time_s_for_dark for extname in tab['camera']] tab['night'] = exp.try_retrieve_header_card('NIGHT', placeholder='') tab['focus'] = exp.try_retrieve_header_card('FOCUS', placeholder='') tab['exptime'] = [exp.images[extname].try_retrieve_meta_keyword('EXPTIME', placeholder=np.nan) for extname in tab['camera']] # hack for acquisition images like guide-00074954-0000.fits.fz # to make sure their _ccds table ends up listing cube_index 0 # rather than NaN is_0000_acq_file = proc_obj.fname_in.find('-0000.fits.fz') != -1 if is_0000_acq_file: tab['cube_index'] = 0 else: tab['cube_index'] = np.nan if cube_index is None else int(cube_index) if cube_index == -1: tab['coadd_index_start'] = [exp.images[extname].coadd_index_range[0] for extname in tab['camera']] tab['coadd_index_end'] = [exp.images[extname].coadd_index_range[1] for extname in tab['camera']] tab['coadd_mjdobs_min'] = [exp.bintables[extname]['MJD-OBS'][ind] for extname, ind in zip(tab['camera'], tab['coadd_index_start'])] tab['coadd_mjdobs_max'] = [exp.bintables[extname]['MJD-OBS'][ind] for extname, ind in zip(tab['camera'], tab['coadd_index_end'])] tab['racen'] = np.zeros(len(tab), dtype=float) tab['deccen'] = np.zeros(len(tab), dtype=float) tab['fname_raw'] = proc_obj.fname_in tab['gitrev'] = proc_obj.gitrev tab['fiber_fracflux'] = [(exp.images[extname].psf.fiber_fracflux if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['fiber_fracflux_elg'] = [(exp.images[extname].psf.fiber_fracflux_elg if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['fiber_fracflux_bgs'] = [(exp.images[extname].psf.fiber_fracflux_bgs if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['n_sources_for_psf'] = [(exp.images[extname].psf.nstars if exp.images[extname].psf is not None else 0) for extname in tab['camera']] # this pertains to aperture _3 which is 1.5 asec radius tab['aper_corr_fac'] = [(exp.images[extname].psf.aper_corr_fac if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['xcentroid_psf'] = [(exp.images[extname].psf.xcen_flux_weighted if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['ycentroid_psf'] = [(exp.images[extname].psf.ycen_flux_weighted if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_fwhm_pix'] = [(exp.images[extname].psf.moffat_fwhm_pix if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_fwhm_asec'] = [(exp.images[extname].psf.moffat_fwhm_asec if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_centroid_cbox'] = [(float(exp.images[extname].psf.cbox) if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_centroid_failed'] = [(exp.images[extname].psf.psf_centroiding_failed if exp.images[extname].psf is not None else 0) for extname in tab['camera']] tab['radprof_fwhm_asec'] = [(exp.images[extname].psf.radprof_fwhm_asec if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] # is 0 the best placeholder value here when no PSF exists? tab['psf_centroiding_flag'] = [(exp.images[extname].psf.psf_centroiding_flag if exp.images[extname].psf is not None else 0) for extname in tab['camera']] tab['psf_asymmetry_ratio'] = [(exp.images[extname].psf.psf_asymmetry_ratio if exp.images[extname].psf is not None else np.float32(np.nan)) for extname in tab['camera']] tab['psf_asymmetry_numerator'] = [(exp.images[extname].psf.psf_asymmetry_numerator if exp.images[extname].psf is not None else np.float32(np.nan)) for extname in tab['camera']] tab['psf_asymmetry_denominator'] = [(exp.images[extname].psf.psf_asymmetry_denominator if exp.images[extname].psf is not None else np.float32(np.nan)) for extname in tab['camera']] tab['psf_total_flux'] = [(exp.images[extname].psf.psf_total_flux if exp.images[extname].psf is not None else np.float32(np.nan)) for extname in tab['camera']] radprof_ccds_table(tab, exp) for i, extname in enumerate(tab['camera']): racen, deccen = ccd_center_radec(exp.images[extname].wcs) tab['racen'][i] = racen tab['deccen'][i] = deccen tab['mountha_header'] = exp.try_retrieve_header_card('MOUNTHA', placeholder=np.nan) tab['mountdec_header'] = exp.try_retrieve_header_card('MOUNTDEC', placeholder=np.nan) tab['ha_deg'] = [util._get_ha(t['skyra'], t['lst_deg'], t['mountdec_header']) for t in tab] tab['ha_deg_per_gfa'] = [util._get_ha(t['racen'], t['lst_deg'], t['mountdec_header']) for t in tab] tab['moon_sep_deg'] = util.moon_separation(tab['moonra'], tab['moondec'], tab['racen'], tab['deccen']) # per-camera zenith distance -- will only be accurate to the extent that # each camera's WCS recalibration succeeded tab['zd_deg_per_gfa'] = [util._zenith_distance(t['racen'], t['deccen'], t['lst_deg']) for t in tab] tab['header_airmass'] = exp.try_retrieve_header_card('AIRMASS', placeholder=np.nan) # this should make the airmass properly evolve # with time for guide cube outputs tab['airmass'] = 1.0/np.cos(tab['zenith_dist_deg']/(180.0/np.pi)) # this per-camera version of airmass should also evolve # properly with time for guide cube outputs tab['airmass_per_gfa'] = 1.0/np.cos(tab['zd_deg_per_gfa']/(180.0/np.pi)) tab['zp_adu_per_s'] = [exp.images[extname].compute_zeropoint(ps1) for extname in tab['camera']] tab['n_stars_for_zp'] = [(np.sum(ps1['use_for_zp'] & (ps1['extname'] == extname)).astype(int) if 'use_for_zp' in ps1.colnames else 0) for extname in tab['camera']] tab['transparency'] = [util.transparency_from_zeropoint(tab[i]['zp_adu_per_s'], tab[i]['airmass_per_gfa'], tab[i]['camera']) for i in range(len(tab))] par = common.gfa_misc_params() tab['kterm'] = np.float32(par['kterm']) tab['fracflux_nominal_pointsource'] = np.float32(par['fracflux_nominal_pointsource']) tab['fracflux_nominal_elg'] = np.float32(par['fracflux_nominal_elg']) tab['fracflux_nominal_bgs'] = np.float32(par['fracflux_nominal_bgs']) tab['det_sn_thresh'] = det_sn_thresh prescan_overscan_ccds_table(tab, exp) high_level_ccds_metrics(tab, catalog, exp) astrom_ccds_table(tab, exp) dark_current_ccds_table(tab, exp) if mjdrange is not None: tab['req_mjd_min'] = mjdrange[0] tab['req_mjd_max'] = mjdrange[1] return tab
def assemble_ccds_table(tab, catalog, exp, outdir, proc_obj, cube_index=None, ps1=None, det_sn_thresh=5.0, sky_mags=True, minimal=False): nrows = len(tab) tab['extname'] = tab['camera'] tab['contrast'] = [ exp.images[extname].header['CONTRAST'] for extname in tab['camera'] ] if minimal: return tab if sky_mags: tab['sky_mag_ab'] = [ exp.images[extname].sky_mag for extname in tab['camera'] ] amps = common.valid_amps_list() if sky_mags: sky_mag_ab_per_amp = np.zeros((nrows, len(amps)), dtype='float32') # 4 amps for i, extname in enumerate(tab['camera']): for j in range(len(amps)): _mag = np.nan if exp.images[ extname].sky_mag_per_amp is None else exp.images[ extname].sky_mag_per_amp[j] sky_mag_ab_per_amp[i, j] = _mag tab['sky_mag_ab_per_amp'] = sky_mag_ab_per_amp tab['petal_loc'] = np.array([ common.gfa_extname_to_gfa_number(extname) for extname in tab['camera'] ], dtype='uint8') tab['expid'] = [ exp.images[extname].header['EXPID'] for extname in tab['camera'] ] # should work except if early versions of guide cubes lacked # MJD information... tab['mjd'] = [ exp.images[extname].try_retrieve_meta_keyword('MJD-OBS', placeholder=0.0) for extname in tab['camera'] ] eph = util.load_lst() tab['lst_deg'] = [util.interp_lst(t['mjd'], eph=eph) for t in tab] tab['program'] = [ str(exp.images[extname].try_retrieve_meta_keyword('PROGRAM', placeholder='')) for extname in tab['camera'] ] tab['skyra'] = [ exp.images[extname].try_retrieve_meta_keyword('SKYRA', placeholder=np.nan) for extname in tab['camera'] ] tab['skydec'] = [ exp.images[extname].try_retrieve_meta_keyword('SKYDEC', placeholder=np.nan) for extname in tab['camera'] ] # zenith distance using approximate center of the field given by SKYRA, SKYDEC tab['zenith_dist_deg'] = [ util._zenith_distance(t['skyra'], t['skydec'], t['lst_deg']) for t in tab ] tab['domshutl'] = np.array( nrows * [exp.try_retrieve_header_card('DOMSHUTL', placeholder='')], dtype='U8') tab['domshutu'] = np.array( nrows * [exp.try_retrieve_header_card('DOMSHUTU', placeholder='')], dtype='U8') tab['pmcover'] = exp.try_retrieve_header_card('PMCOVER', placeholder='') tab['moonra'] = exp.try_retrieve_header_card('MOONRA', placeholder=np.nan) tab['moondec'] = exp.try_retrieve_header_card('MOONDEC', placeholder=np.nan) tab['t_c_for_dark'] = [ exp.images[extname].t_c_for_dark for extname in tab['camera'] ] tab['t_c_for_dark_is_guess'] = [ int(exp.images[extname].t_c_for_dark_is_guess) for extname in tab['camera'] ] tab['time_s_for_dark'] = [ exp.images[extname].time_s_for_dark for extname in tab['camera'] ] tab['night'] = exp.try_retrieve_header_card('NIGHT', placeholder='') tab['focus'] = exp.try_retrieve_header_card('FOCUS', placeholder='') tab['exptime'] = [ exp.images[extname].try_retrieve_meta_keyword('EXPTIME', placeholder=np.nan) for extname in tab['camera'] ] tab['cube_index'] = np.nan if cube_index is None else int(cube_index) if cube_index == -1: tab['coadd_index_start'] = [ exp.images[extname].coadd_index_range[0] for extname in tab['camera'] ] tab['coadd_index_end'] = [ exp.images[extname].coadd_index_range[1] for extname in tab['camera'] ] tab['racen'] = np.zeros(len(tab), dtype=float) tab['deccen'] = np.zeros(len(tab), dtype=float) tab['fname_raw'] = proc_obj.fname_in tab['gitrev'] = proc_obj.gitrev tab['fiber_fracflux'] = [(exp.images[extname].psf.fiber_fracflux if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['n_sources_for_psf'] = [(exp.images[extname].psf.nstars if exp.images[extname].psf is not None else 0) for extname in tab['camera']] # this pertains to aperture _3 which is 1.5 asec radius tab['aper_corr_fac'] = [(exp.images[extname].psf.aper_corr_fac if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['xcentroid_psf'] = [(exp.images[extname].psf.xcen_flux_weighted if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['ycentroid_psf'] = [(exp.images[extname].psf.ycen_flux_weighted if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_fwhm_pix'] = [(exp.images[extname].psf.moffat_fwhm_pix if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_fwhm_asec'] = [(exp.images[extname].psf.moffat_fwhm_asec if exp.images[extname].psf is not None else np.nan) for extname in tab['camera']] tab['psf_centroid_cbox'] = [ (float(exp.images[extname].psf.cbox) if exp.images[extname].psf is not None else np.nan) for extname in tab['camera'] ] tab['psf_centroid_failed'] = [ (exp.images[extname].psf.psf_centroiding_failed if exp.images[extname].psf is not None else 0) for extname in tab['camera'] ] for i, extname in enumerate(tab['camera']): racen, deccen = ccd_center_radec(exp.images[extname].wcs) tab['racen'][i] = racen tab['deccen'][i] = deccen tab['mountha_header'] = exp.try_retrieve_header_card('MOUNTHA', placeholder=np.nan) tab['mountdec_header'] = exp.try_retrieve_header_card('MOUNTDEC', placeholder=np.nan) tab['ha_deg'] = [ util._get_ha(t['skyra'], t['lst_deg'], t['mountdec_header']) for t in tab ] tab['ha_deg_per_gfa'] = [ util._get_ha(t['racen'], t['lst_deg'], t['mountdec_header']) for t in tab ] tab['moon_sep_deg'] = util.moon_separation(tab['moonra'], tab['moondec'], tab['racen'], tab['deccen']) # per-camera zenith distance -- will only be accurate to the extent that # each camera's WCS recalibration succeeded tab['zd_deg_per_gfa'] = [ util._zenith_distance(t['racen'], t['deccen'], t['lst_deg']) for t in tab ] tab['header_airmass'] = exp.try_retrieve_header_card('AIRMASS', placeholder=np.nan) # this should make the airmass properly evolve # with time for guide cube outputs tab['airmass'] = 1.0 / np.cos(tab['zenith_dist_deg'] / (180.0 / np.pi)) # this per-camera version of airmass should also evolve # properly with time for guide cube outputs tab['airmass_per_gfa'] = 1.0 / np.cos(tab['zd_deg_per_gfa'] / (180.0 / np.pi)) tab['zp_adu_per_s'] = [ exp.images[extname].compute_zeropoint(ps1) for extname in tab['camera'] ] tab['transparency'] = [ util.transparency_from_zeropoint(tab[i]['zp_adu_per_s'], tab[i]['airmass_per_gfa'], tab[i]['camera']) for i in range(len(tab)) ] tab['det_sn_thresh'] = det_sn_thresh prescan_overscan_ccds_table(tab, exp) high_level_ccds_metrics(tab, catalog, exp) astrom_ccds_table(tab, exp) dark_current_ccds_table(tab, exp) return tab