def _write_stdstars(self): """Write a fake StdStar model file""" # First generation is very simple stdflux = np.ones((2, self.nwave)) fibers = np.array([1, 4]).astype(int) data = {} data['LOGG'] = 4. * np.ones(fibers.size) data['TEFF'] = 6000. * np.ones(fibers.size) data['FEH'] = np.zeros(fibers.size) data['COEF'] = np.zeros((fibers.size, 12)) # cannot be exactly the same values data['CHI2DOF'] = np.ones(fibers.size) + 0.1 * (fibers % 2) data['REDSHIFT'] = np.zeros(fibers.size) data['DATA_G-R'] = 0.3 * np.ones(fibers.size) data['MODEL_G-R'] = 0.3 * np.ones(fibers.size) # dummy placeholders, not used downstream tmp = np.arange(fibers.size) fibermap = Table() fibermap['TARGETID'] = tmp inframes = Table() inframes['NIGHT'] = tmp inframes['EXPID'] = tmp inframes['CAMERA'] = 'b0' io.write_stdstar_models(self.stdfile, stdflux, self.wave, fibers, data, fibermap, inframes)
def _write_stdstars(self): """Write a fake StdStar model file""" # First generation is very simple stdflux = np.ones((2, self.nwave)) fibers = np.array([1, 4]).astype(int) data = {} data['LOGG'] = 4. * np.ones(fibers.size) data['TEFF'] = 6000. * np.ones(fibers.size) data['FEH'] = np.zeros(fibers.size) data['COEF'] = np.zeros((fibers.size, 12)) # cannot be exactly the same values data['CHI2DOF'] = np.ones(fibers.size) + 0.1 * (fibers % 2) data['REDSHIFT'] = np.zeros(fibers.size) data['DATA_G-R'] = np.zeros(fibers.size) data['MODEL_G-R'] = np.zeros(fibers.size) io.write_stdstar_models(self.stdfile, stdflux, self.wave, fibers, data)
def _write_stdstars(self): """Write a fake StdStar model file""" # First generation is very simple stdflux = np.ones((2, self.nwave)) fibers = np.array([1,4]).astype(int) data={} data['LOGG']=4.*np.ones(fibers.size) data['TEFF']=6000.*np.ones(fibers.size) data['FEH']=np.zeros(fibers.size) data['COEF']=np.zeros((fibers.size,12)) # cannot be exactly the same values data['CHI2DOF']=np.ones(fibers.size)+0.1*(fibers%2) data['REDSHIFT']=np.zeros(fibers.size) data['DATA_G-R']=np.zeros(fibers.size) data['MODEL_G-R']=np.zeros(fibers.size) io.write_stdstar_models(self.stdfile,stdflux,self.wave,fibers,data)
def main(args): """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)" % (args.color, args.delta_color)) frames = {} flats = {} skies = {} spectrograph = None starfibers = None starindices = None fibermap = None # READ DATA ############################################ for filename in args.frames: log.info("reading %s" % filename) frame = io.read_frame(filename) header = fits.getheader(filename, 0) frame_fibermap = frame.fibermap frame_starindices = np.where(frame_fibermap["OBJTYPE"] == "STD")[0] camera = safe_read_key(header, "CAMERA").strip().lower() if spectrograph is None: spectrograph = frame.spectrograph fibermap = frame_fibermap starindices = frame_starindices starfibers = fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph: log.error("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) raise ValueError("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum( starindices != frame_starindices) > 0: log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if frames.has_key(camera): log.error( "cannot handle for now several frame of same camera (%s)" % camera) raise ValueError( "cannot handle for now several frame of same camera (%s)" % camera) frames[camera] = frame for filename in args.skymodels: log.info("reading %s" % filename) sky = io.read_sky(filename) header = fits.getheader(filename, 0) camera = safe_read_key(header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if skies.has_key(camera): log.error("cannot handle several skymodels of same camera (%s)" % camera) raise ValueError( "cannot handle several skymodels of same camera (%s)" % camera) skies[camera] = sky for filename in args.fiberflats: log.info("reading %s" % filename) header = fits.getheader(filename, 0) flat = io.read_fiberflat(filename) camera = safe_read_key(header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if flats.has_key(camera): log.error("cannot handle several flats of same camera (%s)" % camera) raise ValueError( "cannot handle several flats of same camera (%s)" % camera) flats[camera] = flat if starindices.size == 0: log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars" % starindices.size) imaging_filters = fibermap["FILTER"][starindices] imaging_mags = fibermap["MAG"][starindices] log.warning( "NO MAG ERRORS IN FIBERMAP, I AM IGNORING MEASUREMENT ERRORS !!") log.warning( "NO EXTINCTION VALUES IN FIBERMAP, I AM IGNORING THIS FOR NOW !!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ for cam in frames: if not skies.has_key(cam): log.warning("Missing sky for %s" % cam) frames.pop(cam) continue if not flats.has_key(cam): log.warning("Missing flat for %s" % cam) frames.pop(cam) continue frames[cam].flux = frames[cam].flux[starindices] frames[cam].ivar = frames[cam].ivar[starindices] frames[cam].ivar *= (frames[cam].mask[starindices] == 0) frames[cam].ivar *= (skies[cam].ivar[starindices] != 0) frames[cam].ivar *= (skies[cam].mask[starindices] == 0) frames[cam].ivar *= (flats[cam].ivar[starindices] != 0) frames[cam].ivar *= (flats[cam].mask[starindices] == 0) frames[cam].flux *= (frames[cam].ivar > 0) # just for clean plots for star in range(frames[cam].flux.shape[0]): ok = np.where((frames[cam].ivar[star] > 0) & (flats[cam].fiberflat[star] != 0))[0] if ok.size > 0: frames[cam].flux[star] = frames[cam].flux[star] / flats[ cam].fiberflat[star] - skies[cam].flux[star] nstars = starindices.size starindices = None # we don't need this anymore # READ MODELS ############################################ log.info("reading star models in %s" % args.starmodels) stdwave, stdflux, templateid, teff, logg, feh = io.read_stdstar_templates( args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ model_filters = [] for tmp in np.unique(imaging_filters): if len(tmp) > 0: # can be one empty entry model_filters.append(tmp) log.info("computing model mags %s" % model_filters) model_mags = np.zeros((stdflux.shape[0], len(model_filters))) fluxunits = 1e-17 * units.erg / units.s / units.cm**2 / units.Angstrom for index in range(len(model_filters)): filter_response = load_filter(model_filters[index]) for m in range(stdflux.shape[0]): model_mags[m, index] = filter_response.get_ab_magnitude( stdflux[m] * fluxunits, stdwave) log.info("done computing model mags") # LOOP ON STARS TO FIND BEST MODEL ############################################ bestModelIndex = np.arange(nstars) templateID = np.arange(nstars) chi2dof = np.zeros((nstars)) redshift = np.zeros((nstars)) normflux = [] for star in range(nstars): log.info("finding best model for observed star #%d" % star) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames: band = camera[0] wave[band] = frames[camera].wave flux[band] = frames[camera].flux[star] ivar[band] = frames[camera].ivar[star] resolution_data[band] = frames[camera].resolution_data[star] # preselec models based on magnitudes # compute star color index1, index2 = get_color_filter_indices(imaging_filters[star], args.color) if index1 < 0 or index2 < 0: log.error("cannot compute '%s' color from %s" % (color_name, filters)) filter1 = imaging_filters[star][index1] filter2 = imaging_filters[star][index2] star_color = imaging_mags[star][index1] - imaging_mags[star][index2] # compute models color model_index1 = -1 model_index2 = -1 for i, fname in enumerate(model_filters): if fname == filter1: model_index1 = i elif fname == filter2: model_index2 = i if model_index1 < 0 or model_index2 < 0: log.error("cannot compute '%s' model color from %s" % (color_name, filters)) model_colors = model_mags[:, model_index1] - model_mags[:, model_index2] # selection selection = np.where( np.abs(model_colors - star_color) < args.delta_color)[0] log.info( "star#%d fiber #%d, %s = %s-%s = %f, number of pre-selected models = %d/%d" % (star, starfibers[star], args.color, filter1, filter2, star_color, selection.size, stdflux.shape[0])) index_in_selection, redshift[star], chi2dof[star] = match_templates( wave, flux, ivar, resolution_data, stdwave, stdflux[selection], teff[selection], logg[selection], feh[selection], ncpu=args.ncpu, z_max=args.z_max, z_res=args.z_res) bestModelIndex[star] = selection[index_in_selection] log.info( 'Star Fiber: {0}; TemplateID: {1}; Redshift: {2}; Chisq/dof: {3}'. format(starfibers[star], bestModelIndex[star], redshift[star], chi2dof[star])) # Apply redshift to original spectrum at full resolution tmp = np.interp(stdwave, stdwave / (1 + redshift[star]), stdflux[bestModelIndex[star]]) # Normalize the best model using reported magnitude normalizedflux = normalize_templates(stdwave, tmp, imaging_mags[star], imaging_filters[star]) normflux.append(normalizedflux) # Now write the normalized flux for all best models to a file normflux = np.array(normflux) data = {} data['BESTMODEL'] = bestModelIndex data['TEMPLATEID'] = bestModelIndex # IS THAT IT? data['CHI2DOF'] = chi2dof data['REDSHIFT'] = redshift norm_model_file = args.outfile io.write_stdstar_models(args.outfile, normflux, stdwave, starfibers, data)
def main(args) : """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)"%(args.color,args.delta_color)) frames={} flats={} skies={} spectrograph=None starfibers=None starindices=None fibermap=None # READ DATA ############################################ for filename in args.frames : log.info("reading %s"%filename) frame=io.read_frame(filename) header=fits.getheader(filename, 0) frame_fibermap = frame.fibermap frame_starindices = np.where(isStdStar(frame_fibermap['DESI_TARGET']))[0] #- Confirm that all fluxes have entries but trust targeting bits #- to get basic magnitude range correct keep = np.ones(len(frame_starindices), dtype=bool) for colname in ['FLUX_G', 'FLUX_R', 'FLUX_Z']: #- and W1 and W2? keep &= frame_fibermap[colname][frame_starindices] > 10**((22.5-30)/2.5) keep &= frame_fibermap[colname][frame_starindices] < 10**((22.5-0)/2.5) frame_starindices = frame_starindices[keep] camera=safe_read_key(header,"CAMERA").strip().lower() if spectrograph is None : spectrograph = frame.spectrograph fibermap = frame_fibermap starindices=frame_starindices starfibers=fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph : log.error("incompatible spectrographs %d != %d"%(spectrograph,frame.spectrograph)) raise ValueError("incompatible spectrographs %d != %d"%(spectrograph,frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum(starindices!=frame_starindices)>0 : log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if not camera in frames : frames[camera]=[] frames[camera].append(frame) for filename in args.skymodels : log.info("reading %s"%filename) sky=io.read_sky(filename) header=fits.getheader(filename, 0) camera=safe_read_key(header,"CAMERA").strip().lower() if not camera in skies : skies[camera]=[] skies[camera].append(sky) for filename in args.fiberflats : log.info("reading %s"%filename) header=fits.getheader(filename, 0) flat=io.read_fiberflat(filename) camera=safe_read_key(header,"CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if camera in flats: log.warning("cannot handle several flats of same camera (%s), will use only the first one"%camera) #raise ValueError("cannot handle several flats of same camera (%s)"%camera) else : flats[camera]=flat if starindices.size == 0 : log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars"%starindices.size) log.warning("Not using flux errors for Standard Star fits!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ for cam in frames : if not cam in skies: log.warning("Missing sky for %s"%cam) frames.pop(cam) continue if not cam in flats: log.warning("Missing flat for %s"%cam) frames.pop(cam) continue flat=flats[cam] for frame,sky in zip(frames[cam],skies[cam]) : frame.flux = frame.flux[starindices] frame.ivar = frame.ivar[starindices] frame.ivar *= (frame.mask[starindices] == 0) frame.ivar *= (sky.ivar[starindices] != 0) frame.ivar *= (sky.mask[starindices] == 0) frame.ivar *= (flat.ivar[starindices] != 0) frame.ivar *= (flat.mask[starindices] == 0) frame.flux *= ( frame.ivar > 0) # just for clean plots for star in range(frame.flux.shape[0]) : ok=np.where((frame.ivar[star]>0)&(flat.fiberflat[star]!=0))[0] if ok.size > 0 : frame.flux[star] = frame.flux[star]/flat.fiberflat[star] - sky.flux[star] frame.resolution_data = frame.resolution_data[starindices] nstars = starindices.size fibermap = Table(fibermap[starindices]) # READ MODELS ############################################ log.info("reading star models in %s"%args.starmodels) stdwave,stdflux,templateid,teff,logg,feh=io.read_stdstar_templates(args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ #- Support older fibermaps if 'PHOTSYS' not in fibermap.colnames: log.warning('Old fibermap format; using defaults for missing columns') log.warning(" PHOTSYS = 'S'") log.warning(" MW_TRANSMISSION_G/R/Z = 1.0") log.warning(" EBV = 0.0") fibermap['PHOTSYS'] = 'S' fibermap['MW_TRANSMISSION_G'] = 1.0 fibermap['MW_TRANSMISSION_R'] = 1.0 fibermap['MW_TRANSMISSION_Z'] = 1.0 fibermap['EBV'] = 0.0 model_filters = dict() if 'S' in fibermap['PHOTSYS']: for filter_name in ['DECAM_G', 'DECAM_R', 'DECAM_Z']: model_filters[filter_name] = load_filter(filter_name) if 'N' in fibermap['PHOTSYS']: for filter_name in ['BASS_G', 'BASS_R', 'MZLS_Z']: model_filters[filter_name] = load_filter(filter_name) if len(model_filters) == 0: raise ValueError("No filters loaded; neither 'N' nor 'S' in PHOTSYS?") log.info("computing model mags for %s"%sorted(model_filters.keys())) model_mags = dict() fluxunits = 1e-17 * units.erg / units.s / units.cm**2 / units.Angstrom for filter_name, filter_response in model_filters.items(): model_mags[filter_name] = filter_response.get_ab_magnitude(stdflux*fluxunits,stdwave) log.info("done computing model mags") # LOOP ON STARS TO FIND BEST MODEL ############################################ linear_coefficients=np.zeros((nstars,stdflux.shape[0])) chi2dof=np.zeros((nstars)) redshift=np.zeros((nstars)) normflux=[] star_mags = dict() star_unextincted_mags = dict() for band in ['G', 'R', 'Z']: star_mags[band] = 22.5 - 2.5 * np.log10(fibermap['FLUX_'+band]) star_unextincted_mags[band] = 22.5 - 2.5 * np.log10(fibermap['FLUX_'+band] / fibermap['MW_TRANSMISSION_'+band]) star_colors = dict() star_colors['G-R'] = star_mags['G'] - star_mags['R'] star_colors['R-Z'] = star_mags['R'] - star_mags['Z'] star_unextincted_colors = dict() star_unextincted_colors['G-R'] = star_unextincted_mags['G'] - star_unextincted_mags['R'] star_unextincted_colors['R-Z'] = star_unextincted_mags['R'] - star_unextincted_mags['Z'] fitted_model_colors = np.zeros(nstars) for star in range(nstars) : log.info("finding best model for observed star #%d"%star) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames : for i,frame in enumerate(frames[camera]) : identifier="%s-%d"%(camera,i) wave[identifier]=frame.wave flux[identifier]=frame.flux[star] ivar[identifier]=frame.ivar[star] resolution_data[identifier]=frame.resolution_data[star] # preselect models based on magnitudes if fibermap['PHOTSYS'][star] == 'N': if args.color == 'G-R': model_colors = model_mags['BASS_G'] - model_mags['BASS_R'] elif args.color == 'R-Z': model_colors = model_mags['BASS_R'] - model_mags['MZLS_Z'] else: raise ValueError('Unknown color {}'.format(args.color)) else: if args.color == 'G-R': model_colors = model_mags['DECAM_G'] - model_mags['DECAM_R'] elif args.color == 'R-Z': model_colors = model_mags['DECAM_R'] - model_mags['DECAM_Z'] else: raise ValueError('Unknown color {}'.format(args.color)) color_diff = model_colors - star_unextincted_colors[args.color][star] selection = np.abs(color_diff) < args.delta_color # smallest cube in parameter space including this selection (needed for interpolation) new_selection = (teff>=np.min(teff[selection]))&(teff<=np.max(teff[selection])) new_selection &= (logg>=np.min(logg[selection]))&(logg<=np.max(logg[selection])) new_selection &= (feh>=np.min(feh[selection]))&(feh<=np.max(feh[selection])) selection = np.where(new_selection)[0] log.info("star#%d fiber #%d, %s = %f, number of pre-selected models = %d/%d"%( star, starfibers[star], args.color, star_unextincted_colors[args.color][star], selection.size, stdflux.shape[0])) # Match unextincted standard stars to data coefficients, redshift[star], chi2dof[star] = match_templates( wave, flux, ivar, resolution_data, stdwave, stdflux[selection], teff[selection], logg[selection], feh[selection], ncpu=args.ncpu, z_max=args.z_max, z_res=args.z_res, template_error=args.template_error ) linear_coefficients[star,selection] = coefficients log.info('Star Fiber: {0}; TEFF: {1}; LOGG: {2}; FEH: {3}; Redshift: {4}; Chisq/dof: {5}'.format( starfibers[star], np.inner(teff,linear_coefficients[star]), np.inner(logg,linear_coefficients[star]), np.inner(feh,linear_coefficients[star]), redshift[star], chi2dof[star]) ) # Apply redshift to original spectrum at full resolution model=np.zeros(stdwave.size) redshifted_stdwave = stdwave*(1+redshift[star]) for i,c in enumerate(linear_coefficients[star]) : if c != 0 : model += c*np.interp(stdwave,redshifted_stdwave,stdflux[i]) # Apply dust extinction to the model model *= dust_transmission(stdwave, fibermap['EBV'][star]) # Compute final color of dust-extincted model if fibermap['PHOTSYS'][star] == 'N': if args.color == 'G-R': model_mag1 = model_filters['BASS_G'].get_ab_magnitude(model*fluxunits, stdwave) model_mag2 = model_filters['BASS_R'].get_ab_magnitude(model*fluxunits, stdwave) model_magr = model_mag2 elif args.color == 'R-Z': model_mag1 = model_filters['BASS_R'].get_ab_magnitude(model*fluxunits, stdwave) model_mag2 = model_filters['MZLS_Z'].get_ab_magnitude(model*fluxunits, stdwave) model_magr = model_mag1 else: raise ValueError('Unknown color {}'.format(args.color)) else: if args.color == 'G-R': model_mag1 = model_filters['DECAM_G'].get_ab_magnitude(model*fluxunits, stdwave) model_mag2 = model_filters['DECAM_R'].get_ab_magnitude(model*fluxunits, stdwave) model_magr = model_mag2 elif args.color == 'R-Z': model_mag1 = model_filters['DECAM_R'].get_ab_magnitude(model*fluxunits, stdwave) model_mag2 = model_filters['DECAM_Z'].get_ab_magnitude(model*fluxunits, stdwave) model_magr = model_mag1 else: raise ValueError('Unknown color {}'.format(args.color)) fitted_model_colors[star] = model_mag1 - model_mag2 #- TODO: move this back into normalize_templates, at the cost of #- recalculating a model magnitude? # Normalize the best model using reported magnitude scalefac=10**((model_magr - star_mags['R'][star])/2.5) log.info('scaling R mag {} to {} using scale {}'.format(model_magr, star_mags['R'][star], scalefac)) normflux.append(model*scalefac) # Now write the normalized flux for all best models to a file normflux=np.array(normflux) data={} data['LOGG']=linear_coefficients.dot(logg) data['TEFF']= linear_coefficients.dot(teff) data['FEH']= linear_coefficients.dot(feh) data['CHI2DOF']=chi2dof data['REDSHIFT']=redshift data['COEFF']=linear_coefficients data['DATA_%s'%args.color]=star_colors[args.color] data['MODEL_%s'%args.color]=fitted_model_colors io.write_stdstar_models(args.outfile,normflux,stdwave,starfibers,data)
def main(args): """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)" % (args.color, args.delta_color)) frames = {} flats = {} skies = {} spectrograph = None starfibers = None starindices = None fibermap = None # READ DATA ############################################ for filename in args.frames: log.info("reading %s" % filename) frame = io.read_frame(filename) header = fits.getheader(filename, 0) frame_fibermap = frame.fibermap frame_starindices = np.where(isStdStar(frame_fibermap))[0] #- Confirm that all fluxes have entries but trust targeting bits #- to get basic magnitude range correct keep = np.ones(len(frame_starindices), dtype=bool) for colname in ['FLUX_G', 'FLUX_R', 'FLUX_Z']: #- and W1 and W2? keep &= frame_fibermap[colname][frame_starindices] > 10**( (22.5 - 30) / 2.5) keep &= frame_fibermap[colname][frame_starindices] < 10**( (22.5 - 0) / 2.5) frame_starindices = frame_starindices[keep] camera = safe_read_key(header, "CAMERA").strip().lower() if spectrograph is None: spectrograph = frame.spectrograph fibermap = frame_fibermap starindices = frame_starindices starfibers = fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph: log.error("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) raise ValueError("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum( starindices != frame_starindices) > 0: log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if not camera in frames: frames[camera] = [] frames[camera].append(frame) for filename in args.skymodels: log.info("reading %s" % filename) sky = io.read_sky(filename) header = fits.getheader(filename, 0) camera = safe_read_key(header, "CAMERA").strip().lower() if not camera in skies: skies[camera] = [] skies[camera].append(sky) for filename in args.fiberflats: log.info("reading %s" % filename) header = fits.getheader(filename, 0) flat = io.read_fiberflat(filename) camera = safe_read_key(header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if camera in flats: log.warning( "cannot handle several flats of same camera (%s), will use only the first one" % camera) #raise ValueError("cannot handle several flats of same camera (%s)"%camera) else: flats[camera] = flat if starindices.size == 0: log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars" % starindices.size) log.warning("Not using flux errors for Standard Star fits!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ for cam in frames: if not cam in skies: log.warning("Missing sky for %s" % cam) frames.pop(cam) continue if not cam in flats: log.warning("Missing flat for %s" % cam) frames.pop(cam) continue flat = flats[cam] for frame, sky in zip(frames[cam], skies[cam]): frame.flux = frame.flux[starindices] frame.ivar = frame.ivar[starindices] frame.ivar *= (frame.mask[starindices] == 0) frame.ivar *= (sky.ivar[starindices] != 0) frame.ivar *= (sky.mask[starindices] == 0) frame.ivar *= (flat.ivar[starindices] != 0) frame.ivar *= (flat.mask[starindices] == 0) frame.flux *= (frame.ivar > 0) # just for clean plots for star in range(frame.flux.shape[0]): ok = np.where((frame.ivar[star] > 0) & (flat.fiberflat[star] != 0))[0] if ok.size > 0: frame.flux[star] = frame.flux[star] / flat.fiberflat[ star] - sky.flux[star] frame.resolution_data = frame.resolution_data[starindices] nstars = starindices.size fibermap = Table(fibermap[starindices]) # READ MODELS ############################################ log.info("reading star models in %s" % args.starmodels) stdwave, stdflux, templateid, teff, logg, feh = io.read_stdstar_templates( args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ #- Support older fibermaps if 'PHOTSYS' not in fibermap.colnames: log.warning('Old fibermap format; using defaults for missing columns') log.warning(" PHOTSYS = 'S'") log.warning(" MW_TRANSMISSION_G/R/Z = 1.0") log.warning(" EBV = 0.0") fibermap['PHOTSYS'] = 'S' fibermap['MW_TRANSMISSION_G'] = 1.0 fibermap['MW_TRANSMISSION_R'] = 1.0 fibermap['MW_TRANSMISSION_Z'] = 1.0 fibermap['EBV'] = 0.0 model_filters = dict() if 'S' in fibermap['PHOTSYS']: for filter_name in ['DECAM_G', 'DECAM_R', 'DECAM_Z']: model_filters[filter_name] = load_filter(filter_name) if 'N' in fibermap['PHOTSYS']: for filter_name in ['BASS_G', 'BASS_R', 'MZLS_Z']: model_filters[filter_name] = load_filter(filter_name) if len(model_filters) == 0: raise ValueError("No filters loaded; neither 'N' nor 'S' in PHOTSYS?") log.info("computing model mags for %s" % sorted(model_filters.keys())) model_mags = dict() fluxunits = 1e-17 * units.erg / units.s / units.cm**2 / units.Angstrom for filter_name, filter_response in model_filters.items(): model_mags[filter_name] = filter_response.get_ab_magnitude( stdflux * fluxunits, stdwave) log.info("done computing model mags") # LOOP ON STARS TO FIND BEST MODEL ############################################ linear_coefficients = np.zeros((nstars, stdflux.shape[0])) chi2dof = np.zeros((nstars)) redshift = np.zeros((nstars)) normflux = [] star_mags = dict() star_unextincted_mags = dict() for band in ['G', 'R', 'Z']: star_mags[band] = 22.5 - 2.5 * np.log10(fibermap['FLUX_' + band]) star_unextincted_mags[band] = 22.5 - 2.5 * np.log10( fibermap['FLUX_' + band] / fibermap['MW_TRANSMISSION_' + band]) star_colors = dict() star_colors['G-R'] = star_mags['G'] - star_mags['R'] star_colors['R-Z'] = star_mags['R'] - star_mags['Z'] star_unextincted_colors = dict() star_unextincted_colors[ 'G-R'] = star_unextincted_mags['G'] - star_unextincted_mags['R'] star_unextincted_colors[ 'R-Z'] = star_unextincted_mags['R'] - star_unextincted_mags['Z'] fitted_model_colors = np.zeros(nstars) for star in range(nstars): log.info("finding best model for observed star #%d" % star) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames: for i, frame in enumerate(frames[camera]): identifier = "%s-%d" % (camera, i) wave[identifier] = frame.wave flux[identifier] = frame.flux[star] ivar[identifier] = frame.ivar[star] resolution_data[identifier] = frame.resolution_data[star] # preselect models based on magnitudes if fibermap['PHOTSYS'][star] == 'N': if args.color == 'G-R': model_colors = model_mags['BASS_G'] - model_mags['BASS_R'] elif args.color == 'R-Z': model_colors = model_mags['BASS_R'] - model_mags['MZLS_Z'] else: raise ValueError('Unknown color {}'.format(args.color)) else: if args.color == 'G-R': model_colors = model_mags['DECAM_G'] - model_mags['DECAM_R'] elif args.color == 'R-Z': model_colors = model_mags['DECAM_R'] - model_mags['DECAM_Z'] else: raise ValueError('Unknown color {}'.format(args.color)) color_diff = model_colors - star_unextincted_colors[args.color][star] selection = np.abs(color_diff) < args.delta_color # smallest cube in parameter space including this selection (needed for interpolation) new_selection = (teff >= np.min(teff[selection])) & (teff <= np.max( teff[selection])) new_selection &= (logg >= np.min(logg[selection])) & (logg <= np.max( logg[selection])) new_selection &= (feh >= np.min(feh[selection])) & (feh <= np.max( feh[selection])) selection = np.where(new_selection)[0] log.info( "star#%d fiber #%d, %s = %f, number of pre-selected models = %d/%d" % (star, starfibers[star], args.color, star_unextincted_colors[args.color][star], selection.size, stdflux.shape[0])) # Match unextincted standard stars to data coefficients, redshift[star], chi2dof[star] = match_templates( wave, flux, ivar, resolution_data, stdwave, stdflux[selection], teff[selection], logg[selection], feh[selection], ncpu=args.ncpu, z_max=args.z_max, z_res=args.z_res, template_error=args.template_error) linear_coefficients[star, selection] = coefficients log.info( 'Star Fiber: {0}; TEFF: {1}; LOGG: {2}; FEH: {3}; Redshift: {4}; Chisq/dof: {5}' .format(starfibers[star], np.inner(teff, linear_coefficients[star]), np.inner(logg, linear_coefficients[star]), np.inner(feh, linear_coefficients[star]), redshift[star], chi2dof[star])) # Apply redshift to original spectrum at full resolution model = np.zeros(stdwave.size) redshifted_stdwave = stdwave * (1 + redshift[star]) for i, c in enumerate(linear_coefficients[star]): if c != 0: model += c * np.interp(stdwave, redshifted_stdwave, stdflux[i]) # Apply dust extinction to the model model *= dust_transmission(stdwave, fibermap['EBV'][star]) # Compute final color of dust-extincted model if fibermap['PHOTSYS'][star] == 'N': if args.color == 'G-R': model_mag1 = model_filters['BASS_G'].get_ab_magnitude( model * fluxunits, stdwave) model_mag2 = model_filters['BASS_R'].get_ab_magnitude( model * fluxunits, stdwave) model_magr = model_mag2 elif args.color == 'R-Z': model_mag1 = model_filters['BASS_R'].get_ab_magnitude( model * fluxunits, stdwave) model_mag2 = model_filters['MZLS_Z'].get_ab_magnitude( model * fluxunits, stdwave) model_magr = model_mag1 else: raise ValueError('Unknown color {}'.format(args.color)) else: if args.color == 'G-R': model_mag1 = model_filters['DECAM_G'].get_ab_magnitude( model * fluxunits, stdwave) model_mag2 = model_filters['DECAM_R'].get_ab_magnitude( model * fluxunits, stdwave) model_magr = model_mag2 elif args.color == 'R-Z': model_mag1 = model_filters['DECAM_R'].get_ab_magnitude( model * fluxunits, stdwave) model_mag2 = model_filters['DECAM_Z'].get_ab_magnitude( model * fluxunits, stdwave) model_magr = model_mag1 else: raise ValueError('Unknown color {}'.format(args.color)) fitted_model_colors[star] = model_mag1 - model_mag2 #- TODO: move this back into normalize_templates, at the cost of #- recalculating a model magnitude? # Normalize the best model using reported magnitude scalefac = 10**((model_magr - star_mags['R'][star]) / 2.5) log.info('scaling R mag {} to {} using scale {}'.format( model_magr, star_mags['R'][star], scalefac)) normflux.append(model * scalefac) # Now write the normalized flux for all best models to a file normflux = np.array(normflux) data = {} data['LOGG'] = linear_coefficients.dot(logg) data['TEFF'] = linear_coefficients.dot(teff) data['FEH'] = linear_coefficients.dot(feh) data['CHI2DOF'] = chi2dof data['REDSHIFT'] = redshift data['COEFF'] = linear_coefficients data['DATA_%s' % args.color] = star_colors[args.color] data['MODEL_%s' % args.color] = fitted_model_colors io.write_stdstar_models(args.outfile, normflux, stdwave, starfibers, data)
def main(args): """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)" % (args.color, args.delta_color)) frames = {} flats = {} skies = {} spectrograph = None starfibers = None starindices = None fibermap = None # READ DATA ############################################ for filename in args.frames: log.info("reading %s" % filename) frame = io.read_frame(filename) header = fits.getheader(filename, 0) frame_fibermap = frame.fibermap frame_starindices = np.where(frame_fibermap["OBJTYPE"] == "STD")[0] # check magnitude are well defined or discard stars tmp = [] for i in frame_starindices: mags = frame_fibermap["MAG"][i] ok = np.sum((mags > 0) & (mags < 30)) if np.sum((mags > 0) & (mags < 30)) == mags.size: tmp.append(i) frame_starindices = np.array(tmp).astype(int) camera = safe_read_key(header, "CAMERA").strip().lower() if spectrograph is None: spectrograph = frame.spectrograph fibermap = frame_fibermap starindices = frame_starindices starfibers = fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph: log.error("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) raise ValueError("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum( starindices != frame_starindices) > 0: log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if not camera in frames: frames[camera] = [] frames[camera].append(frame) for filename in args.skymodels: log.info("reading %s" % filename) sky = io.read_sky(filename) header = fits.getheader(filename, 0) camera = safe_read_key(header, "CAMERA").strip().lower() if not camera in skies: skies[camera] = [] skies[camera].append(sky) for filename in args.fiberflats: log.info("reading %s" % filename) header = fits.getheader(filename, 0) flat = io.read_fiberflat(filename) camera = safe_read_key(header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if camera in flats: log.warning( "cannot handle several flats of same camera (%s), will use only the first one" % camera) #raise ValueError("cannot handle several flats of same camera (%s)"%camera) else: flats[camera] = flat if starindices.size == 0: log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars" % starindices.size) imaging_filters = fibermap["FILTER"][starindices] imaging_mags = fibermap["MAG"][starindices] log.warning( "NO MAG ERRORS IN FIBERMAP, I AM IGNORING MEASUREMENT ERRORS !!") ebv = np.zeros(starindices.size) if "SFD_EBV" in fibermap.columns.names: log.info("Using 'SFD_EBV' from fibermap") ebv = fibermap["SFD_EBV"][starindices] else: log.warning("NO EXTINCTION VALUES IN FIBERMAP!!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ for cam in frames: if not cam in skies: log.warning("Missing sky for %s" % cam) frames.pop(cam) continue if not cam in flats: log.warning("Missing flat for %s" % cam) frames.pop(cam) continue flat = flats[cam] for frame, sky in zip(frames[cam], skies[cam]): frame.flux = frame.flux[starindices] frame.ivar = frame.ivar[starindices] frame.ivar *= (frame.mask[starindices] == 0) frame.ivar *= (sky.ivar[starindices] != 0) frame.ivar *= (sky.mask[starindices] == 0) frame.ivar *= (flat.ivar[starindices] != 0) frame.ivar *= (flat.mask[starindices] == 0) frame.flux *= (frame.ivar > 0) # just for clean plots for star in range(frame.flux.shape[0]): ok = np.where((frame.ivar[star] > 0) & (flat.fiberflat[star] != 0))[0] if ok.size > 0: frame.flux[star] = frame.flux[star] / flat.fiberflat[ star] - sky.flux[star] frame.resolution_data = frame.resolution_data[starindices] nstars = starindices.size starindices = None # we don't need this anymore # READ MODELS ############################################ log.info("reading star models in %s" % args.starmodels) stdwave, stdflux, templateid, teff, logg, feh = io.read_stdstar_templates( args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ model_filters = [] for tmp in np.unique(imaging_filters): if len(tmp) > 0: # can be one empty entry model_filters.append(tmp) log.info("computing model mags %s" % model_filters) model_mags = np.zeros((stdflux.shape[0], len(model_filters))) fluxunits = 1e-17 * units.erg / units.s / units.cm**2 / units.Angstrom for index in range(len(model_filters)): if model_filters[index].startswith('WISE'): log.warning('not computing stdstar {} mags'.format( model_filters[index])) continue filter_response = load_filter(model_filters[index]) for m in range(stdflux.shape[0]): model_mags[m, index] = filter_response.get_ab_magnitude( stdflux[m] * fluxunits, stdwave) log.info("done computing model mags") mean_extinction_delta_mags = None mean_ebv = np.mean(ebv) if mean_ebv > 0: log.info( "Compute a mean delta_color from average E(B-V) = %3.2f based on canonial model star" % mean_ebv) # compute a mean delta_color from mean_ebv based on canonial model star ####################################################################### # will then use this color offset in the model pre-selection # find canonical f-type model: Teff=6000, logg=4, Fe/H=-1.5 canonical_model = np.argmin((teff - 6000.0)**2 + (logg - 4.0)**2 + (feh + 1.5)**2) canonical_model_mags_without_extinction = model_mags[canonical_model] canonical_model_mags_with_extinction = np.zeros( canonical_model_mags_without_extinction.shape) canonical_model_reddened_flux = stdflux[ canonical_model] * dust_transmission(stdwave, mean_ebv) for index in range(len(model_filters)): if model_filters[index].startswith('WISE'): log.warning('not computing stdstar {} mags'.format( model_filters[index])) continue filter_response = load_filter(model_filters[index]) canonical_model_mags_with_extinction[ index] = filter_response.get_ab_magnitude( canonical_model_reddened_flux * fluxunits, stdwave) mean_extinction_delta_mags = canonical_model_mags_with_extinction - canonical_model_mags_without_extinction # LOOP ON STARS TO FIND BEST MODEL ############################################ linear_coefficients = np.zeros((nstars, stdflux.shape[0])) chi2dof = np.zeros((nstars)) redshift = np.zeros((nstars)) normflux = [] star_colors_array = np.zeros((nstars)) model_colors_array = np.zeros((nstars)) for star in range(nstars): log.info("finding best model for observed star #%d" % star) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames: for i, frame in enumerate(frames[camera]): identifier = "%s-%d" % (camera, i) wave[identifier] = frame.wave flux[identifier] = frame.flux[star] ivar[identifier] = frame.ivar[star] resolution_data[identifier] = frame.resolution_data[star] # preselec models based on magnitudes # compute star color index1, index2 = get_color_filter_indices(imaging_filters[star], args.color) if index1 < 0 or index2 < 0: log.error("cannot compute '%s' color from %s" % (color_name, filters)) filter1 = imaging_filters[star][index1] filter2 = imaging_filters[star][index2] star_color = imaging_mags[star][index1] - imaging_mags[star][index2] star_colors_array[star] = star_color # compute models color model_index1 = -1 model_index2 = -1 for i, fname in enumerate(model_filters): if fname == filter1: model_index1 = i elif fname == filter2: model_index2 = i if model_index1 < 0 or model_index2 < 0: log.error("cannot compute '%s' model color from %s" % (color_name, filters)) model_colors = model_mags[:, model_index1] - model_mags[:, model_index2] # apply extinction here # use the colors derived from the cannonical model with the mean ebv of the stars # and simply apply a scaling factor based on the ebv of this star # this is sufficiently precise for the broad model pre-selection we are doing here # the exact reddening of the star to each pre-selected model is # apply afterwards if mean_extinction_delta_mags is not None and mean_ebv != 0: delta_color = (mean_extinction_delta_mags[model_index1] - mean_extinction_delta_mags[model_index2] ) * ebv[star] / mean_ebv model_colors += delta_color log.info( "Apply a %s-%s color offset = %4.3f to the models for star with E(B-V)=%4.3f" % (model_filters[model_index1], model_filters[model_index2], delta_color, ebv[star])) # selection selection = np.abs(model_colors - star_color) < args.delta_color # smallest cube in parameter space including this selection (needed for interpolation) new_selection = (teff >= np.min(teff[selection])) & (teff <= np.max( teff[selection])) new_selection &= (logg >= np.min(logg[selection])) & (logg <= np.max( logg[selection])) new_selection &= (feh >= np.min(feh[selection])) & (feh <= np.max( feh[selection])) selection = np.where(new_selection)[0] log.info( "star#%d fiber #%d, %s = %s-%s = %f, number of pre-selected models = %d/%d" % (star, starfibers[star], args.color, filter1, filter2, star_color, selection.size, stdflux.shape[0])) # apply extinction to selected_models dust_transmission_of_this_star = dust_transmission(stdwave, ebv[star]) selected_reddened_stdflux = stdflux[ selection] * dust_transmission_of_this_star coefficients, redshift[star], chi2dof[star] = match_templates( wave, flux, ivar, resolution_data, stdwave, selected_reddened_stdflux, teff[selection], logg[selection], feh[selection], ncpu=args.ncpu, z_max=args.z_max, z_res=args.z_res, template_error=args.template_error) linear_coefficients[star, selection] = coefficients log.info( 'Star Fiber: {0}; TEFF: {1}; LOGG: {2}; FEH: {3}; Redshift: {4}; Chisq/dof: {5}' .format(starfibers[star], np.inner(teff, linear_coefficients[star]), np.inner(logg, linear_coefficients[star]), np.inner(feh, linear_coefficients[star]), redshift[star], chi2dof[star])) # Apply redshift to original spectrum at full resolution model = np.zeros(stdwave.size) for i, c in enumerate(linear_coefficients[star]): if c != 0: model += c * np.interp(stdwave, stdwave * (1 + redshift[star]), stdflux[i]) # Apply dust extinction model *= dust_transmission_of_this_star # Compute final model color mag1 = load_filter(model_filters[model_index1]).get_ab_magnitude( model * fluxunits, stdwave) mag2 = load_filter(model_filters[model_index2]).get_ab_magnitude( model * fluxunits, stdwave) model_colors_array[star] = mag1 - mag2 # Normalize the best model using reported magnitude normalizedflux = normalize_templates(stdwave, model, imaging_mags[star], imaging_filters[star]) normflux.append(normalizedflux) # Now write the normalized flux for all best models to a file normflux = np.array(normflux) data = {} data['LOGG'] = linear_coefficients.dot(logg) data['TEFF'] = linear_coefficients.dot(teff) data['FEH'] = linear_coefficients.dot(feh) data['CHI2DOF'] = chi2dof data['REDSHIFT'] = redshift data['COEFF'] = linear_coefficients data['DATA_%s' % args.color] = star_colors_array data['MODEL_%s' % args.color] = model_colors_array norm_model_file = args.outfile io.write_stdstar_models(args.outfile, normflux, stdwave, starfibers, data)
def main(args): """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)" % (args.color, args.delta_color)) log.info('multiprocess parallelizing with {} processes'.format(args.ncpu)) # READ DATA ############################################ # First loop through and group by exposure and spectrograph frames_by_expid = {} for filename in args.frames: log.info("reading %s" % filename) frame = io.read_frame(filename) expid = safe_read_key(frame.meta, "EXPID") camera = safe_read_key(frame.meta, "CAMERA").strip().lower() spec = camera[1] uniq_key = (expid, spec) if uniq_key in frames_by_expid.keys(): frames_by_expid[uniq_key][camera] = frame else: frames_by_expid[uniq_key] = {camera: frame} frames = {} flats = {} skies = {} spectrograph = None starfibers = None starindices = None fibermap = None # For each unique expid,spec pair, get the logical OR of the FIBERSTATUS for all # cameras and then proceed with extracting the frame information # once we modify the fibermap FIBERSTATUS for (expid, spec), camdict in frames_by_expid.items(): fiberstatus = None for frame in camdict.values(): if fiberstatus is None: fiberstatus = frame.fibermap['FIBERSTATUS'].data.copy() else: fiberstatus |= frame.fibermap['FIBERSTATUS'] for camera, frame in camdict.items(): frame.fibermap['FIBERSTATUS'] |= fiberstatus # Set fibermask flagged spectra to have 0 flux and variance frame = get_fiberbitmasked_frame(frame, bitmask='stdstars', ivar_framemask=True) frame_fibermap = frame.fibermap frame_starindices = np.where(isStdStar(frame_fibermap))[0] #- Confirm that all fluxes have entries but trust targeting bits #- to get basic magnitude range correct keep = np.ones(len(frame_starindices), dtype=bool) for colname in ['FLUX_G', 'FLUX_R', 'FLUX_Z']: #- and W1 and W2? keep &= frame_fibermap[colname][frame_starindices] > 10**( (22.5 - 30) / 2.5) keep &= frame_fibermap[colname][frame_starindices] < 10**( (22.5 - 0) / 2.5) frame_starindices = frame_starindices[keep] if spectrograph is None: spectrograph = frame.spectrograph fibermap = frame_fibermap starindices = frame_starindices starfibers = fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph: log.error("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) raise ValueError("incompatible spectrographs %d != %d" % (spectrograph, frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum( starindices != frame_starindices) > 0: log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if not camera in frames: frames[camera] = [] frames[camera].append(frame) # possibly cleanup memory del frames_by_expid for filename in args.skymodels: log.info("reading %s" % filename) sky = io.read_sky(filename) camera = safe_read_key(sky.header, "CAMERA").strip().lower() if not camera in skies: skies[camera] = [] skies[camera].append(sky) for filename in args.fiberflats: log.info("reading %s" % filename) flat = io.read_fiberflat(filename) camera = safe_read_key(flat.header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if camera in flats: log.warning( "cannot handle several flats of same camera (%s), will use only the first one" % camera) #raise ValueError("cannot handle several flats of same camera (%s)"%camera) else: flats[camera] = flat if starindices.size == 0: log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars" % starindices.size) log.warning("Not using flux errors for Standard Star fits!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ # since poping dict, we need to copy keys to iterate over to avoid # RuntimeError due to changing dict frame_cams = list(frames.keys()) for cam in frame_cams: if not cam in skies: log.warning("Missing sky for %s" % cam) frames.pop(cam) continue if not cam in flats: log.warning("Missing flat for %s" % cam) frames.pop(cam) continue flat = flats[cam] for frame, sky in zip(frames[cam], skies[cam]): frame.flux = frame.flux[starindices] frame.ivar = frame.ivar[starindices] frame.ivar *= (frame.mask[starindices] == 0) frame.ivar *= (sky.ivar[starindices] != 0) frame.ivar *= (sky.mask[starindices] == 0) frame.ivar *= (flat.ivar[starindices] != 0) frame.ivar *= (flat.mask[starindices] == 0) frame.flux *= (frame.ivar > 0) # just for clean plots for star in range(frame.flux.shape[0]): ok = np.where((frame.ivar[star] > 0) & (flat.fiberflat[star] != 0))[0] if ok.size > 0: frame.flux[star] = frame.flux[star] / flat.fiberflat[ star] - sky.flux[star] frame.resolution_data = frame.resolution_data[starindices] # CHECK S/N ############################################ # for each band in 'brz', record quadratic sum of median S/N across wavelength snr = dict() for band in ['b', 'r', 'z']: snr[band] = np.zeros(starindices.size) for cam in frames: band = cam[0].lower() for frame in frames[cam]: msnr = np.median(frame.flux * np.sqrt(frame.ivar) / np.sqrt(np.gradient(frame.wave)), axis=1) # median SNR per sqrt(A.) msnr *= (msnr > 0) snr[band] = np.sqrt(snr[band]**2 + msnr**2) log.info("SNR(B) = {}".format(snr['b'])) ############################### max_number_of_stars = 50 min_blue_snr = 4. ############################### indices = np.argsort(snr['b'])[::-1][:max_number_of_stars] validstars = np.where(snr['b'][indices] > min_blue_snr)[0] #- TODO: later we filter on models based upon color, thus throwing #- away very blue stars for which we don't have good models. log.info("Number of stars with median stacked blue S/N > {} /sqrt(A) = {}". format(min_blue_snr, validstars.size)) if validstars.size == 0: log.error("No valid star") sys.exit(12) validstars = indices[validstars] for band in ['b', 'r', 'z']: snr[band] = snr[band][validstars] log.info("BLUE SNR of selected stars={}".format(snr['b'])) for cam in frames: for frame in frames[cam]: frame.flux = frame.flux[validstars] frame.ivar = frame.ivar[validstars] frame.resolution_data = frame.resolution_data[validstars] starindices = starindices[validstars] starfibers = starfibers[validstars] nstars = starindices.size fibermap = Table(fibermap[starindices]) # MASK OUT THROUGHPUT DIP REGION ############################################ mask_throughput_dip_region = True if mask_throughput_dip_region: wmin = 4300. wmax = 4500. log.warning( "Masking out the wavelength region [{},{}]A in the standard star fit" .format(wmin, wmax)) for cam in frames: for frame in frames[cam]: ii = np.where((frame.wave >= wmin) & (frame.wave <= wmax))[0] if ii.size > 0: frame.ivar[:, ii] = 0 # READ MODELS ############################################ log.info("reading star models in %s" % args.starmodels) stdwave, stdflux, templateid, teff, logg, feh = io.read_stdstar_templates( args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ #- Support older fibermaps if 'PHOTSYS' not in fibermap.colnames: log.warning('Old fibermap format; using defaults for missing columns') log.warning(" PHOTSYS = 'S'") log.warning(" MW_TRANSMISSION_G/R/Z = 1.0") log.warning(" EBV = 0.0") fibermap['PHOTSYS'] = 'S' fibermap['MW_TRANSMISSION_G'] = 1.0 fibermap['MW_TRANSMISSION_R'] = 1.0 fibermap['MW_TRANSMISSION_Z'] = 1.0 fibermap['EBV'] = 0.0 model_filters = dict() for band in ["G", "R", "Z"]: for photsys in np.unique(fibermap['PHOTSYS']): model_filters[band + photsys] = load_legacy_survey_filter( band=band, photsys=photsys) log.info("computing model mags for %s" % sorted(model_filters.keys())) model_mags = dict() fluxunits = 1e-17 * units.erg / units.s / units.cm**2 / units.Angstrom for filter_name, filter_response in model_filters.items(): model_mags[filter_name] = filter_response.get_ab_magnitude( stdflux * fluxunits, stdwave) log.info("done computing model mags") # LOOP ON STARS TO FIND BEST MODEL ############################################ linear_coefficients = np.zeros((nstars, stdflux.shape[0])) chi2dof = np.zeros((nstars)) redshift = np.zeros((nstars)) normflux = [] star_mags = dict() star_unextincted_mags = dict() for band in ['G', 'R', 'Z']: star_mags[band] = 22.5 - 2.5 * np.log10(fibermap['FLUX_' + band]) star_unextincted_mags[band] = 22.5 - 2.5 * np.log10( fibermap['FLUX_' + band] / fibermap['MW_TRANSMISSION_' + band]) star_colors = dict() star_colors['G-R'] = star_mags['G'] - star_mags['R'] star_colors['R-Z'] = star_mags['R'] - star_mags['Z'] star_unextincted_colors = dict() star_unextincted_colors[ 'G-R'] = star_unextincted_mags['G'] - star_unextincted_mags['R'] star_unextincted_colors[ 'R-Z'] = star_unextincted_mags['R'] - star_unextincted_mags['Z'] fitted_model_colors = np.zeros(nstars) for star in range(nstars): log.info("finding best model for observed star #%d" % star) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames: for i, frame in enumerate(frames[camera]): identifier = "%s-%d" % (camera, i) wave[identifier] = frame.wave flux[identifier] = frame.flux[star] ivar[identifier] = frame.ivar[star] resolution_data[identifier] = frame.resolution_data[star] # preselect models based on magnitudes photsys = fibermap['PHOTSYS'][star] if not args.color in ['G-R', 'R-Z']: raise ValueError('Unknown color {}'.format(args.color)) bands = args.color.split("-") model_colors = model_mags[bands[0] + photsys] - model_mags[bands[1] + photsys] color_diff = model_colors - star_unextincted_colors[args.color][star] selection = np.abs(color_diff) < args.delta_color if np.sum(selection) == 0: log.warning("no model in the selected color range for this star") continue # smallest cube in parameter space including this selection (needed for interpolation) new_selection = (teff >= np.min(teff[selection])) & (teff <= np.max( teff[selection])) new_selection &= (logg >= np.min(logg[selection])) & (logg <= np.max( logg[selection])) new_selection &= (feh >= np.min(feh[selection])) & (feh <= np.max( feh[selection])) selection = np.where(new_selection)[0] log.info( "star#%d fiber #%d, %s = %f, number of pre-selected models = %d/%d" % (star, starfibers[star], args.color, star_unextincted_colors[args.color][star], selection.size, stdflux.shape[0])) # Match unextincted standard stars to data coefficients, redshift[star], chi2dof[star] = match_templates( wave, flux, ivar, resolution_data, stdwave, stdflux[selection], teff[selection], logg[selection], feh[selection], ncpu=args.ncpu, z_max=args.z_max, z_res=args.z_res, template_error=args.template_error) linear_coefficients[star, selection] = coefficients log.info( 'Star Fiber: {}; TEFF: {:.3f}; LOGG: {:.3f}; FEH: {:.3f}; Redshift: {:g}; Chisq/dof: {:.3f}' .format(starfibers[star], np.inner(teff, linear_coefficients[star]), np.inner(logg, linear_coefficients[star]), np.inner(feh, linear_coefficients[star]), redshift[star], chi2dof[star])) # Apply redshift to original spectrum at full resolution model = np.zeros(stdwave.size) redshifted_stdwave = stdwave * (1 + redshift[star]) for i, c in enumerate(linear_coefficients[star]): if c != 0: model += c * np.interp(stdwave, redshifted_stdwave, stdflux[i]) # Apply dust extinction to the model log.info("Applying MW dust extinction to star {} with EBV = {}".format( star, fibermap['EBV'][star])) model *= dust_transmission(stdwave, fibermap['EBV'][star]) # Compute final color of dust-extincted model photsys = fibermap['PHOTSYS'][star] if not args.color in ['G-R', 'R-Z']: raise ValueError('Unknown color {}'.format(args.color)) bands = args.color.split("-") model_mag1 = model_filters[bands[0] + photsys].get_ab_magnitude( model * fluxunits, stdwave) model_mag2 = model_filters[bands[1] + photsys].get_ab_magnitude( model * fluxunits, stdwave) fitted_model_colors[star] = model_mag1 - model_mag2 if bands[0] == "R": model_magr = model_mag1 elif bands[1] == "R": model_magr = model_mag2 #- TODO: move this back into normalize_templates, at the cost of #- recalculating a model magnitude? # Normalize the best model using reported magnitude scalefac = 10**((model_magr - star_mags['R'][star]) / 2.5) log.info('scaling R mag {:.3f} to {:.3f} using scale {}'.format( model_magr, star_mags['R'][star], scalefac)) normflux.append(model * scalefac) # Now write the normalized flux for all best models to a file normflux = np.array(normflux) fitted_stars = np.where(chi2dof != 0)[0] if fitted_stars.size == 0: log.error("No star has been fit.") sys.exit(12) data = {} data['LOGG'] = linear_coefficients[fitted_stars, :].dot(logg) data['TEFF'] = linear_coefficients[fitted_stars, :].dot(teff) data['FEH'] = linear_coefficients[fitted_stars, :].dot(feh) data['CHI2DOF'] = chi2dof[fitted_stars] data['REDSHIFT'] = redshift[fitted_stars] data['COEFF'] = linear_coefficients[fitted_stars, :] data['DATA_%s' % args.color] = star_colors[args.color][fitted_stars] data['MODEL_%s' % args.color] = fitted_model_colors[fitted_stars] data['BLUE_SNR'] = snr['b'][fitted_stars] data['RED_SNR'] = snr['r'][fitted_stars] data['NIR_SNR'] = snr['z'][fitted_stars] io.write_stdstar_models(args.outfile, normflux, stdwave, starfibers[fitted_stars], data)
def main(args, comm=None): """ finds the best models of all standard stars in the frame and normlize the model flux. Output is written to a file and will be called for calibration. """ log = get_logger() log.info("mag delta %s = %f (for the pre-selection of stellar models)" % (args.color, args.delta_color)) if args.mpi or comm is not None: from mpi4py import MPI if comm is None: comm = MPI.COMM_WORLD size = comm.Get_size() rank = comm.Get_rank() if rank == 0: log.info('mpi parallelizing with {} ranks'.format(size)) else: comm = None rank = 0 size = 1 # disable multiprocess by forcing ncpu = 1 when using MPI if comm is not None: ncpu = 1 if rank == 0: log.info('disabling multiprocess (forcing ncpu = 1)') else: ncpu = args.ncpu if ncpu > 1: if rank == 0: log.info( 'multiprocess parallelizing with {} processes'.format(ncpu)) if args.ignore_gpu and desispec.fluxcalibration.use_gpu: # Opt-out of GPU usage desispec.fluxcalibration.use_gpu = False if rank == 0: log.info('ignoring GPU') elif desispec.fluxcalibration.use_gpu: # Nothing to do here, GPU is used by default if available if rank == 0: log.info('using GPU') else: if rank == 0: log.info('GPU not available') std_targetids = None if args.std_targetids is not None: std_targetids = args.std_targetids # READ DATA ############################################ # First loop through and group by exposure and spectrograph frames_by_expid = {} rows = list() for filename in args.frames: log.info("reading %s" % filename) frame = io.read_frame(filename) night = safe_read_key(frame.meta, "NIGHT") expid = safe_read_key(frame.meta, "EXPID") camera = safe_read_key(frame.meta, "CAMERA").strip().lower() rows.append((night, expid, camera)) spec = camera[1] uniq_key = (expid, spec) if uniq_key in frames_by_expid.keys(): frames_by_expid[uniq_key][camera] = frame else: frames_by_expid[uniq_key] = {camera: frame} input_frames_table = Table(rows=rows, names=('NIGHT', 'EXPID', 'TILEID')) frames = {} flats = {} skies = {} spectrograph = None starfibers = None starindices = None fibermap = None # For each unique expid,spec pair, get the logical OR of the FIBERSTATUS for all # cameras and then proceed with extracting the frame information # once we modify the fibermap FIBERSTATUS for (expid, spec), camdict in frames_by_expid.items(): fiberstatus = None for frame in camdict.values(): if fiberstatus is None: fiberstatus = frame.fibermap['FIBERSTATUS'].data.copy() else: fiberstatus |= frame.fibermap['FIBERSTATUS'] for camera, frame in camdict.items(): frame.fibermap['FIBERSTATUS'] |= fiberstatus # Set fibermask flagged spectra to have 0 flux and variance frame = get_fiberbitmasked_frame(frame, bitmask='stdstars', ivar_framemask=True) frame_fibermap = frame.fibermap if std_targetids is None: frame_starindices = np.where(isStdStar(frame_fibermap))[0] else: frame_starindices = np.nonzero( np.isin(frame_fibermap['TARGETID'], std_targetids))[0] #- Confirm that all fluxes have entries but trust targeting bits #- to get basic magnitude range correct keep_legacy = np.ones(len(frame_starindices), dtype=bool) for colname in ['FLUX_G', 'FLUX_R', 'FLUX_Z']: #- and W1 and W2? keep_legacy &= frame_fibermap[colname][ frame_starindices] > 10**((22.5 - 30) / 2.5) keep_legacy &= frame_fibermap[colname][ frame_starindices] < 10**((22.5 - 0) / 2.5) keep_gaia = np.ones(len(frame_starindices), dtype=bool) for colname in ['G', 'BP', 'RP']: #- and W1 and W2? keep_gaia &= frame_fibermap[ 'GAIA_PHOT_' + colname + '_MEAN_MAG'][frame_starindices] > 10 keep_gaia &= frame_fibermap[ 'GAIA_PHOT_' + colname + '_MEAN_MAG'][frame_starindices] < 20 n_legacy_std = keep_legacy.sum() n_gaia_std = keep_gaia.sum() keep = keep_legacy | keep_gaia # accept both types of standards for the time being # keep the indices for gaia/legacy subsets gaia_indices = keep_gaia[keep] legacy_indices = keep_legacy[keep] frame_starindices = frame_starindices[keep] if spectrograph is None: spectrograph = frame.spectrograph fibermap = frame_fibermap starindices = frame_starindices starfibers = fibermap["FIBER"][starindices] elif spectrograph != frame.spectrograph: log.error("incompatible spectrographs {} != {}".format( spectrograph, frame.spectrograph)) raise ValueError("incompatible spectrographs {} != {}".format( spectrograph, frame.spectrograph)) elif starindices.size != frame_starindices.size or np.sum( starindices != frame_starindices) > 0: log.error("incompatible fibermap") raise ValueError("incompatible fibermap") if not camera in frames: frames[camera] = [] frames[camera].append(frame) # possibly cleanup memory del frames_by_expid for filename in args.skymodels: log.info("reading %s" % filename) sky = io.read_sky(filename) camera = safe_read_key(sky.header, "CAMERA").strip().lower() if not camera in skies: skies[camera] = [] skies[camera].append(sky) for filename in args.fiberflats: log.info("reading %s" % filename) flat = io.read_fiberflat(filename) camera = safe_read_key(flat.header, "CAMERA").strip().lower() # NEED TO ADD MORE CHECKS if camera in flats: log.warning( "cannot handle several flats of same camera (%s), will use only the first one" % camera) #raise ValueError("cannot handle several flats of same camera (%s)"%camera) else: flats[camera] = flat # if color is not specified we decide on the fly color = args.color if color is not None: if color[:4] == 'GAIA': legacy_color = False gaia_color = True else: legacy_color = True gaia_color = False if n_legacy_std == 0 and legacy_color: raise Exception( 'Specified Legacy survey color, but no legacy standards') if n_gaia_std == 0 and gaia_color: raise Exception('Specified gaia color, but no gaia stds') if starindices.size == 0: log.error("no STD star found in fibermap") raise ValueError("no STD star found in fibermap") log.info("found %d STD stars" % starindices.size) if n_legacy_std == 0: gaia_std = True if color is None: color = 'GAIA-BP-RP' else: gaia_std = False if color is None: color = 'G-R' if n_gaia_std > 0: log.info('Gaia standards found but not used') if gaia_std: # The name of the reference filter to which we normalize the flux ref_mag_name = 'GAIA-G' color_band1, color_band2 = ['GAIA-' + _ for _ in color[5:].split('-')] log.info( "Using Gaia standards with color {} and normalizing to {}".format( color, ref_mag_name)) # select appropriate subset of standards starindices = starindices[gaia_indices] starfibers = starfibers[gaia_indices] else: ref_mag_name = 'R' color_band1, color_band2 = color.split('-') log.info("Using Legacy standards with color {} and normalizing to {}". format(color, ref_mag_name)) # select appropriate subset of standards starindices = starindices[legacy_indices] starfibers = starfibers[legacy_indices] # excessive check but just in case if not color in ['G-R', 'R-Z', 'GAIA-BP-RP', 'GAIA-G-RP']: raise ValueError('Unknown color {}'.format(color)) # log.warning("Not using flux errors for Standard Star fits!") # DIVIDE FLAT AND SUBTRACT SKY , TRIM DATA ############################################ # since poping dict, we need to copy keys to iterate over to avoid # RuntimeError due to changing dict frame_cams = list(frames.keys()) for cam in frame_cams: if not cam in skies: log.warning("Missing sky for %s" % cam) frames.pop(cam) continue if not cam in flats: log.warning("Missing flat for %s" % cam) frames.pop(cam) continue flat = flats[cam] for frame, sky in zip(frames[cam], skies[cam]): frame.flux = frame.flux[starindices] frame.ivar = frame.ivar[starindices] frame.ivar *= (frame.mask[starindices] == 0) frame.ivar *= (sky.ivar[starindices] != 0) frame.ivar *= (sky.mask[starindices] == 0) frame.ivar *= (flat.ivar[starindices] != 0) frame.ivar *= (flat.mask[starindices] == 0) frame.flux *= (frame.ivar > 0) # just for clean plots for star in range(frame.flux.shape[0]): ok = np.where((frame.ivar[star] > 0) & (flat.fiberflat[star] != 0))[0] if ok.size > 0: frame.flux[star] = frame.flux[star] / flat.fiberflat[ star] - sky.flux[star] frame.resolution_data = frame.resolution_data[starindices] nframes = len(frames[cam]) if nframes > 1: # optimal weights for the coaddition = ivar*throughput, not directly ivar, # we estimate the relative throughput with median fluxes at this stage medflux = np.zeros(nframes) for i, frame in enumerate(frames[cam]): if np.sum(frame.ivar > 0) == 0: log.error( "ivar=0 for all std star spectra in frame {}-{:08d}". format(cam, frame.meta["EXPID"])) else: medflux[i] = np.median(frame.flux[frame.ivar > 0]) log.debug("medflux = {}".format(medflux)) medflux *= (medflux > 0) if np.sum(medflux > 0) == 0: log.error( "mean median flux = 0, for all stars in fibers {}".format( list(frames[cam][0].fibermap["FIBER"][starindices]))) sys.exit(12) mmedflux = np.mean(medflux[medflux > 0]) weights = medflux / mmedflux log.info("coadding {} exposures in cam {}, w={}".format( nframes, cam, weights)) sw = np.zeros(frames[cam][0].flux.shape) swf = np.zeros(frames[cam][0].flux.shape) swr = np.zeros(frames[cam][0].resolution_data.shape) for i, frame in enumerate(frames[cam]): sw += weights[i] * frame.ivar swf += weights[i] * frame.ivar * frame.flux swr += weights[i] * frame.ivar[:, None, :] * frame.resolution_data coadded_frame = frames[cam][0] coadded_frame.ivar = sw coadded_frame.flux = swf / (sw + (sw == 0)) coadded_frame.resolution_data = swr / ((sw + (sw == 0))[:, None, :]) frames[cam] = [coadded_frame] # CHECK S/N ############################################ # for each band in 'brz', record quadratic sum of median S/N across wavelength snr = dict() for band in ['b', 'r', 'z']: snr[band] = np.zeros(starindices.size) for cam in frames: band = cam[0].lower() for frame in frames[cam]: msnr = np.median(frame.flux * np.sqrt(frame.ivar) / np.sqrt(np.gradient(frame.wave)), axis=1) # median SNR per sqrt(A.) msnr *= (msnr > 0) snr[band] = np.sqrt(snr[band]**2 + msnr**2) log.info("SNR(B) = {}".format(snr['b'])) ############################### max_number_of_stars = 50 min_blue_snr = 4. ############################### indices = np.argsort(snr['b'])[::-1][:max_number_of_stars] validstars = np.where(snr['b'][indices] > min_blue_snr)[0] #- TODO: later we filter on models based upon color, thus throwing #- away very blue stars for which we don't have good models. log.info("Number of stars with median stacked blue S/N > {} /sqrt(A) = {}". format(min_blue_snr, validstars.size)) if validstars.size == 0: log.error("No valid star") sys.exit(12) validstars = indices[validstars] for band in ['b', 'r', 'z']: snr[band] = snr[band][validstars] log.info("BLUE SNR of selected stars={}".format(snr['b'])) for cam in frames: for frame in frames[cam]: frame.flux = frame.flux[validstars] frame.ivar = frame.ivar[validstars] frame.resolution_data = frame.resolution_data[validstars] starindices = starindices[validstars] starfibers = starfibers[validstars] nstars = starindices.size fibermap = Table(fibermap[starindices]) # MASK OUT THROUGHPUT DIP REGION ############################################ mask_throughput_dip_region = True if mask_throughput_dip_region: wmin = 4300. wmax = 4500. log.warning( "Masking out the wavelength region [{},{}]A in the standard star fit" .format(wmin, wmax)) for cam in frames: for frame in frames[cam]: ii = np.where((frame.wave >= wmin) & (frame.wave <= wmax))[0] if ii.size > 0: frame.ivar[:, ii] = 0 # READ MODELS ############################################ log.info("reading star models in %s" % args.starmodels) stdwave, stdflux, templateid, teff, logg, feh = io.read_stdstar_templates( args.starmodels) # COMPUTE MAGS OF MODELS FOR EACH STD STAR MAG ############################################ #- Support older fibermaps if 'PHOTSYS' not in fibermap.colnames: log.warning('Old fibermap format; using defaults for missing columns') log.warning(" PHOTSYS = 'S'") log.warning(" EBV = 0.0") fibermap['PHOTSYS'] = 'S' fibermap['EBV'] = 0.0 if not np.in1d(np.unique(fibermap['PHOTSYS']), ['', 'N', 'S', 'G']).all(): log.error('Unknown PHOTSYS found') raise Exception('Unknown PHOTSYS found') # Fetching Filter curves model_filters = dict() for band in ["G", "R", "Z"]: for photsys in np.unique(fibermap['PHOTSYS']): if photsys in ['N', 'S']: model_filters[band + photsys] = load_legacy_survey_filter( band=band, photsys=photsys) if len(model_filters) == 0: log.info('No Legacy survey photometry identified in fibermap') # I will always load gaia data even if we are fitting LS standards only for band in ["G", "BP", "RP"]: model_filters["GAIA-" + band] = load_gaia_filter(band=band, dr=2) # Compute model mags on rank 0 and bcast result to other ranks # This sidesteps an OOM event on Cori Haswell with "-c 2" model_mags = None if rank == 0: log.info("computing model mags for %s" % sorted(model_filters.keys())) model_mags = dict() for filter_name in model_filters.keys(): model_mags[filter_name] = get_magnitude(stdwave, stdflux, model_filters, filter_name) log.info("done computing model mags") if comm is not None: model_mags = comm.bcast(model_mags, root=0) # LOOP ON STARS TO FIND BEST MODEL ############################################ star_mags = dict() star_unextincted_mags = dict() if gaia_std and (fibermap['EBV'] == 0).all(): log.info("Using E(B-V) from SFD rather than FIBERMAP") # when doing gaia standards, on old tiles the # EBV is not set so we fetch from SFD (in original SFD scaling) ebv = SFDMap(scaling=1).ebv( acoo.SkyCoord(ra=fibermap['TARGET_RA'] * units.deg, dec=fibermap['TARGET_DEC'] * units.deg)) else: ebv = fibermap['EBV'] photometric_systems = np.unique(fibermap['PHOTSYS']) if not gaia_std: for band in ['G', 'R', 'Z']: star_mags[band] = 22.5 - 2.5 * np.log10(fibermap['FLUX_' + band]) star_unextincted_mags[band] = np.zeros(star_mags[band].shape) for photsys in photometric_systems: r_band = extinction_total_to_selective_ratio( band, photsys) # dimensionless # r_band = a_band / E(B-V) # E(B-V) is a difference of magnitudes (dimensionless) # a_band = -2.5*log10(effective dust transmission) , dimensionless # effective dust transmission = # integral( SED(lambda) * filter_transmission(lambda,band) * dust_transmission(lambda,E(B-V)) dlamdba) # / integral( SED(lambda) * filter_transmission(lambda,band) dlamdba) selection = (fibermap['PHOTSYS'] == photsys) a_band = r_band * ebv[selection] # dimensionless star_unextincted_mags[band][selection] = 22.5 - 2.5 * np.log10( fibermap['FLUX_' + band][selection]) - a_band for band in ['G', 'BP', 'RP']: star_mags['GAIA-' + band] = fibermap['GAIA_PHOT_' + band + '_MEAN_MAG'] for band, extval in gaia_extinction(star_mags['GAIA-G'], star_mags['GAIA-BP'], star_mags['GAIA-RP'], ebv).items(): star_unextincted_mags['GAIA-' + band] = star_mags['GAIA-' + band] - extval star_colors = dict() star_unextincted_colors = dict() # compute the colors and define the unextincted colors # the unextincted colors are filled later if not gaia_std: for c1, c2 in ['GR', 'RZ']: star_colors[c1 + '-' + c2] = star_mags[c1] - star_mags[c2] star_unextincted_colors[c1 + '-' + c2] = (star_unextincted_mags[c1] - star_unextincted_mags[c2]) for c1, c2 in [('BP', 'RP'), ('G', 'RP')]: star_colors['GAIA-' + c1 + '-' + c2] = (star_mags['GAIA-' + c1] - star_mags['GAIA-' + c2]) star_unextincted_colors['GAIA-' + c1 + '-' + c2] = (star_unextincted_mags['GAIA-' + c1] - star_unextincted_mags['GAIA-' + c2]) linear_coefficients = np.zeros((nstars, stdflux.shape[0])) chi2dof = np.zeros((nstars)) redshift = np.zeros((nstars)) normflux = np.zeros((nstars, stdwave.size)) fitted_model_colors = np.zeros(nstars) local_comm, head_comm = None, None if comm is not None: # All ranks in local_comm work on the same stars local_comm = comm.Split(rank % nstars, rank) # The color 1 in head_comm contains all ranks that are have rank 0 in local_comm head_comm = comm.Split(rank < nstars, rank) for star in range(rank % nstars, nstars, size): log.info("rank %d: finding best model for observed star #%d" % (rank, star)) # np.array of wave,flux,ivar,resol wave = {} flux = {} ivar = {} resolution_data = {} for camera in frames: for i, frame in enumerate(frames[camera]): identifier = "%s-%d" % (camera, i) wave[identifier] = frame.wave flux[identifier] = frame.flux[star] ivar[identifier] = frame.ivar[star] resolution_data[identifier] = frame.resolution_data[star] # preselect models based on magnitudes photsys = fibermap['PHOTSYS'][star] if gaia_std: model_colors = model_mags[color_band1] - model_mags[color_band2] else: model_colors = model_mags[color_band1 + photsys] - model_mags[color_band2 + photsys] color_diff = model_colors - star_unextincted_colors[color][star] selection = np.abs(color_diff) < args.delta_color if np.sum(selection) == 0: log.warning("no model in the selected color range for this star") continue # smallest cube in parameter space including this selection (needed for interpolation) new_selection = (teff >= np.min(teff[selection])) & (teff <= np.max( teff[selection])) new_selection &= (logg >= np.min(logg[selection])) & (logg <= np.max( logg[selection])) new_selection &= (feh >= np.min(feh[selection])) & (feh <= np.max( feh[selection])) selection = np.where(new_selection)[0] log.info( "star#%d fiber #%d, %s = %f, number of pre-selected models = %d/%d" % (star, starfibers[star], color, star_unextincted_colors[color][star], selection.size, stdflux.shape[0])) # Match unextincted standard stars to data match_templates_result = match_templates( wave, flux, ivar, resolution_data, stdwave, stdflux[selection], teff[selection], logg[selection], feh[selection], ncpu=ncpu, z_max=args.z_max, z_res=args.z_res, template_error=args.template_error, comm=local_comm) # Only local rank 0 can perform the remaining work if local_comm is not None and local_comm.Get_rank() != 0: continue coefficients, redshift[star], chi2dof[star] = match_templates_result linear_coefficients[star, selection] = coefficients log.info( 'Star Fiber: {}; TEFF: {:.3f}; LOGG: {:.3f}; FEH: {:.3f}; Redshift: {:g}; Chisq/dof: {:.3f}' .format(starfibers[star], np.inner(teff, linear_coefficients[star]), np.inner(logg, linear_coefficients[star]), np.inner(feh, linear_coefficients[star]), redshift[star], chi2dof[star])) # Apply redshift to original spectrum at full resolution model = np.zeros(stdwave.size) redshifted_stdwave = stdwave * (1 + redshift[star]) for i, c in enumerate(linear_coefficients[star]): if c != 0: model += c * np.interp(stdwave, redshifted_stdwave, stdflux[i]) # Apply dust extinction to the model log.info("Applying MW dust extinction to star {} with EBV = {}".format( star, ebv[star])) model *= dust_transmission(stdwave, ebv[star]) # Compute final color of dust-extincted model photsys = fibermap['PHOTSYS'][star] if not gaia_std: model_mag1, model_mag2 = [ get_magnitude(stdwave, model, model_filters, _ + photsys) for _ in [color_band1, color_band2] ] else: model_mag1, model_mag2 = [ get_magnitude(stdwave, model, model_filters, _) for _ in [color_band1, color_band2] ] if color_band1 == ref_mag_name: model_magr = model_mag1 elif color_band2 == ref_mag_name: model_magr = model_mag2 else: # if the reference magnitude is not among colours # I'm fetching it separately. This will happen when # colour is BP-RP and ref magnitude is G if gaia_std: model_magr = get_magnitude(stdwave, model, model_filters, ref_mag_name) else: model_magr = get_magnitude(stdwave, model, model_filters, ref_mag_name + photsys) fitted_model_colors[star] = model_mag1 - model_mag2 #- TODO: move this back into normalize_templates, at the cost of #- recalculating a model magnitude? cur_refmag = star_mags[ref_mag_name][star] # Normalize the best model using reported magnitude scalefac = 10**((model_magr - cur_refmag) / 2.5) log.info('scaling {} mag {:.3f} to {:.3f} using scale {}'.format( ref_mag_name, model_magr, cur_refmag, scalefac)) normflux[star] = model * scalefac if head_comm is not None and rank < nstars: # head_comm color is 1 linear_coefficients = head_comm.reduce(linear_coefficients, op=MPI.SUM, root=0) redshift = head_comm.reduce(redshift, op=MPI.SUM, root=0) chi2dof = head_comm.reduce(chi2dof, op=MPI.SUM, root=0) fitted_model_colors = head_comm.reduce(fitted_model_colors, op=MPI.SUM, root=0) normflux = head_comm.reduce(normflux, op=MPI.SUM, root=0) # Check at least one star was fit. The check is peformed on rank 0 and # the result is bcast to other ranks so that all ranks exit together if # the check fails. atleastonestarfit = False if rank == 0: fitted_stars = np.where(chi2dof != 0)[0] atleastonestarfit = fitted_stars.size > 0 if comm is not None: atleastonestarfit = comm.bcast(atleastonestarfit, root=0) if not atleastonestarfit: log.error("No star has been fit.") sys.exit(12) # Now write the normalized flux for all best models to a file if rank == 0: # get the fibermap from any input frame for the standard stars fibermap = Table(frame.fibermap) keep = np.isin(fibermap['FIBER'], starfibers[fitted_stars]) fibermap = fibermap[keep] # drop fibermap columns specific to exposures instead of targets for col in [ 'DELTA_X', 'DELTA_Y', 'EXPTIME', 'NUM_ITER', 'FIBER_RA', 'FIBER_DEC', 'FIBER_X', 'FIBER_Y' ]: if col in fibermap.colnames: fibermap.remove_column(col) data = {} data['LOGG'] = linear_coefficients[fitted_stars, :].dot(logg) data['TEFF'] = linear_coefficients[fitted_stars, :].dot(teff) data['FEH'] = linear_coefficients[fitted_stars, :].dot(feh) data['CHI2DOF'] = chi2dof[fitted_stars] data['REDSHIFT'] = redshift[fitted_stars] data['COEFF'] = linear_coefficients[fitted_stars, :] data['DATA_%s' % color] = star_colors[color][fitted_stars] data['MODEL_%s' % color] = fitted_model_colors[fitted_stars] data['BLUE_SNR'] = snr['b'][fitted_stars] data['RED_SNR'] = snr['r'][fitted_stars] data['NIR_SNR'] = snr['z'][fitted_stars] io.write_stdstar_models(args.outfile, normflux, stdwave, starfibers[fitted_stars], data, fibermap, input_frames_table)