def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: generate ds9 region files associated ' 'to a particular rectification and wavelength calibration' ) # required arguments parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) # optional arguments parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # generate RectWaveCoeff object rectwv_coeff = RectWaveCoeff._datatype_load( args.rectwv_coeff.name) save_four_ds9(rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) save_spectral_lines_ds9(rectwv_coeff=rectwv_coeff, debugplot=args.debugplot)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser() # required arguments parser.add_argument("--input_rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration polynomials " "corresponding to a longslit observation", type=argparse.FileType('rt')) parser.add_argument("--output_rectwv_coeff", required=True, help="Output JSON file with updated longslit_model " "coefficients", type=lambda x: arg_file_is_new(parser, x, mode='wt')) # optional arguments parser.add_argument("--geometry", help="tuple x,y,dx,dy (default 0,0,640,480)", default="0,0,640,480") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- logging_from_debugplot(args.debugplot) logger = logging.getLogger(__name__) # geometry if args.geometry is None: geometry = None else: tmp_str = args.geometry.split(",") x_geom = int(tmp_str[0]) y_geom = int(tmp_str[1]) dx_geom = int(tmp_str[2]) dy_geom = int(tmp_str[3]) geometry = x_geom, y_geom, dx_geom, dy_geom # generate RectWaveCoeff object rectwv_coeff = RectWaveCoeff._datatype_load(args.input_rectwv_coeff.name) # update longslit_model parameters rectwv_coeff_updated = rectwv_coeff_add_longslit_model( rectwv_coeff=rectwv_coeff, geometry=geometry, debugplot=args.debugplot) # save updated RectWaveCoeff object into JSON file rectwv_coeff_updated.writeto(args.output_rectwv_coeff.name) logger.info('>>> Saving file ' + args.output_rectwv_coeff.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: apply rectification polynomials ' 'for the CSU configuration of a particular image' ) # required arguments parser.add_argument("fitsfile", help="Input FITS file", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--outfile", required=True, help="Output FITS file with rectified image", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_dtu_configuration", help="Ignore DTU configurations differences between " "transformation and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # read calibration structure from JSON file rectwv_coeff = RectWaveCoeff._datatype_load( args.rectwv_coeff.name) # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) header = hdulist[0].header image2d = hdulist[0].data hdulist.close() # protections naxis2, naxis1 = image2d.shape if naxis1 != header['naxis1'] or naxis2 != header['naxis2']: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) raise ValueError('Something is wrong with NAXIS1 and/or NAXIS2') if abs(args.debugplot) >= 10: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) # check that the input FITS file grism and filter match filter_name = header['filter'] if filter_name != rectwv_coeff.tags['filter']: raise ValueError("Filter name does not match!") grism_name = header['grism'] if grism_name != rectwv_coeff.tags['grism']: raise ValueError("Filter name does not match!") if abs(args.debugplot) >= 10: print('>>> grism.......:', grism_name) print('>>> filter......:', filter_name) # check that the DTU configurations are compatible dtu_conf_fitsfile = DtuConfiguration.define_from_fits(args.fitsfile) dtu_conf_jsonfile = DtuConfiguration.define_from_dictionary( rectwv_coeff.meta_info['dtu_configuration']) if dtu_conf_fitsfile != dtu_conf_jsonfile: print('DTU configuration (FITS file):\n\t', dtu_conf_fitsfile) print('DTU configuration (JSON file):\n\t', dtu_conf_jsonfile) if args.ignore_dtu_configuration: print('WARNING: DTU configuration differences found!') else: raise ValueError("DTU configurations do not match!") else: if abs(args.debugplot) >= 10: print('>>> DTU Configuration match!') print(dtu_conf_fitsfile) # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in rectwv_coeff.missing_slitlets: list_valid_islitlets.remove(idel) if abs(args.debugplot) >= 10: print('>>> valid slitlet numbers:\n', list_valid_islitlets) naxis2_enlarged = EMIR_NBARS * EMIR_NPIXPERSLIT_RECTIFIED image2d_rectified = np.zeros((naxis2_enlarged, EMIR_NAXIS1)) image2d_unrectified = np.zeros((EMIR_NAXIS2, EMIR_NAXIS1)) for islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) # extract 2D image corresponding to the selected slitlet: note that # in this case we are not using select_unrectified_slitlets() # because it introduces extra zero pixels in the slitlet frontiers slitlet2d = slt.extract_slitlet2d(image2d) # rectify image slitlet2d_rect = slt.rectify(slitlet2d, resampling=args.resampling) # minimum and maximum useful row in the full 2d rectified image # (starting from 0) i1 = slt.iminslt - 1 i2 = slt.imaxslt # minimum and maximum scan in the rectified slitlet # (in pixels, from 1 to NAXIS2) ii1 = slt.min_row_rectified ii2 = slt.max_row_rectified + 1 # save rectified slitlet in its corresponding location within # the full 2d rectified image image2d_rectified[i1:i2, :] = slitlet2d_rect[ii1:ii2, :] # --- # unrectify image slitlet2d_unrect = slt.rectify(slitlet2d_rect, resampling=args.resampling, inverse=True) # minimum and maximum useful scan (pixel in the spatial direction) # for the rectified slitlet nscan_min, nscan_max = nscan_minmax_frontiers( slt.y0_frontier_lower, slt.y0_frontier_upper, resize=False ) ii1 = nscan_min - slt.bb_ns1_orig ii2 = nscan_max - slt.bb_ns1_orig + 1 j1 = slt.bb_nc1_orig - 1 j2 = slt.bb_nc2_orig i1 = slt.bb_ns1_orig - 1 + ii1 i2 = i1 + ii2 - ii1 image2d_unrectified[i1:i2, j1:j2] = slitlet2d_unrect[ii1:ii2, :] if args.debugplot == 0: print('OK!') save_ndarray_to_fits( array=[image2d_rectified, image2d_unrectified], file_name=args.outfile, cast_to_float=[True] * 2, overwrite=True ) print('>>> Saving file ' + args.outfile.name)
def rectwv_coeff_from_arc_image(reduced_image, bound_param, lines_catalog, args_nbrightlines=None, args_ymargin_bb=2, args_remove_sp_background=True, args_times_sigma_threshold=10, args_order_fmap=2, args_sigma_gaussian_filtering=2, args_margin_npix=50, args_poldeg_initial=3, args_poldeg_refined=5, args_interactive=False, args_threshold_wv=0, args_ylogscale=False, args_pdf=None, args_geometry=(0, 0, 640, 480), debugplot=0): """Evaluate rect.+wavecal. coefficients from arc image Parameters ---------- reduced_image : HDUList object Image with preliminary basic reduction: bpm, bias, dark and flatfield. bound_param : RefinedBoundaryModelParam instance Refined boundary model. lines_catalog : Numpy array 2D numpy array with the contents of the master file with the expected arc line wavelengths. args_nbrightlines : int TBD args_ymargin_bb : int TBD args_remove_sp_background : bool TBD args_times_sigma_threshold : float TBD args_order_fmap : int TBD args_sigma_gaussian_filtering : float TBD args_margin_npix : int TBD args_poldeg_initial : int TBD args_poldeg_refined : int TBD args_interactive : bool TBD args_threshold_wv : float TBD args_ylogscale : bool TBD args_pdf : TBD args_geometry : TBD debugplot : int Debugging level for messages and plots. For details see 'numina.array.display.pause_debugplot.py'. Returns ------- rectwv_coeff : RectWaveCoeff instance Rectification and wavelength calibration coefficients for the particular CSU configuration of the input arc image. reduced_55sp : HDUList object Image with 55 spectra corresponding to the median spectrum for each slitlet, employed to derived the wavelength calibration polynomial. """ logger = logging.getLogger(__name__) # protections if args_interactive and args_pdf is not None: logger.error('--interactive and --pdf are incompatible options') raise ValueError('--interactive and --pdf are incompatible options') # header and data array header = reduced_image[0].header image2d = reduced_image[0].data # check grism and filter filter_name = header['filter'] logger.info('Filter: ' + filter_name) if filter_name != bound_param.tags['filter']: raise ValueError('Filter name does not match!') grism_name = header['grism'] logger.info('Grism: ' + grism_name) if grism_name != bound_param.tags['grism']: raise ValueError('Grism name does not match!') # read the CSU configuration from the image header csu_conf = CsuConfiguration.define_from_header(header) logger.debug(csu_conf) # read the DTU configuration from the image header dtu_conf = DtuConfiguration.define_from_header(header) logger.debug(dtu_conf) # set boundary parameters parmodel = bound_param.meta_info['parmodel'] params = bound_params_from_dict(bound_param.__getstate__()) if abs(debugplot) >= 10: print('-' * 83) print('* FITTED BOUND PARAMETERS') params.pretty_print() pause_debugplot(debugplot) # determine parameters according to grism+filter combination wv_parameters = set_wv_parameters(filter_name, grism_name) islitlet_min = wv_parameters['islitlet_min'] islitlet_max = wv_parameters['islitlet_max'] if args_nbrightlines is None: nbrightlines = wv_parameters['nbrightlines'] else: nbrightlines = [int(idum) for idum in args_nbrightlines.split(',')] poly_crval1_linear = wv_parameters['poly_crval1_linear'] poly_cdelt1_linear = wv_parameters['poly_cdelt1_linear'] wvmin_expected = wv_parameters['wvmin_expected'] wvmax_expected = wv_parameters['wvmax_expected'] wvmin_useful = wv_parameters['wvmin_useful'] wvmax_useful = wv_parameters['wvmax_useful'] # list of slitlets to be computed logger.info('list_slitlets: [' + str(islitlet_min) + ',... ' + str(islitlet_max) + ']') # read master arc line wavelengths (only brightest lines) wv_master = read_wv_master_from_array(master_table=lines_catalog, lines='brightest', debugplot=debugplot) # read master arc line wavelengths (whole data set) wv_master_all = read_wv_master_from_array(master_table=lines_catalog, lines='all', debugplot=debugplot) # check that the arc lines in the master file are properly sorted # in ascending order for i in range(len(wv_master_all) - 1): if wv_master_all[i] >= wv_master_all[i + 1]: logger.error('>>> wavelengths: ' + str(wv_master_all[i]) + ' ' + str(wv_master_all[i + 1])) raise ValueError('Arc lines are not sorted in master file') # --- image2d_55sp = np.zeros((EMIR_NBARS, EMIR_NAXIS1)) # compute rectification transformation and wavelength calibration # polynomials measured_slitlets = [] cout = '0' for islitlet in range(1, EMIR_NBARS + 1): if islitlet_min <= islitlet <= islitlet_max: # define Slitlet2dArc object slt = Slitlet2dArc(islitlet=islitlet, csu_conf=csu_conf, ymargin_bb=args_ymargin_bb, params=params, parmodel=parmodel, debugplot=debugplot) # extract 2D image corresponding to the selected slitlet, clipping # the image beyond the unrectified slitlet (in order to isolate # the arc lines of the current slitlet; otherwise there are # problems with arc lines from neighbour slitlets) image2d_tmp = select_unrectified_slitlet( image2d=image2d, islitlet=islitlet, csu_bar_slit_center=csu_conf.csu_bar_slit_center(islitlet), params=params, parmodel=parmodel, maskonly=False) slitlet2d = slt.extract_slitlet2d(image2d_tmp) # subtract smooth background computed as follows: # - median collapsed spectrum of the whole slitlet2d # - independent median filtering of the previous spectrum in the # two halves in the spectral direction if args_remove_sp_background: spmedian = np.median(slitlet2d, axis=0) naxis1_tmp = spmedian.shape[0] jmidpoint = naxis1_tmp // 2 sp1 = medfilt(spmedian[:jmidpoint], [201]) sp2 = medfilt(spmedian[jmidpoint:], [201]) spbackground = np.concatenate((sp1, sp2)) slitlet2d -= spbackground # locate unknown arc lines slt.locate_unknown_arc_lines( slitlet2d=slitlet2d, times_sigma_threshold=args_times_sigma_threshold) # continue working with current slitlet only if arc lines have # been detected if slt.list_arc_lines is not None: # compute intersections between spectrum trails and arc lines slt.xy_spectrail_arc_intersections(slitlet2d=slitlet2d) # compute rectification transformation slt.estimate_tt_to_rectify(order=args_order_fmap, slitlet2d=slitlet2d) # rectify image slitlet2d_rect = slt.rectify(slitlet2d, resampling=2, transformation=1) # median spectrum and line peaks from rectified image sp_median, fxpeaks = slt.median_spectrum_from_rectified_image( slitlet2d_rect, sigma_gaussian_filtering=args_sigma_gaussian_filtering, nwinwidth_initial=5, nwinwidth_refined=5, times_sigma_threshold=5, npix_avoid_border=6, nbrightlines=nbrightlines) image2d_55sp[islitlet - 1, :] = sp_median # determine expected wavelength limits prior to the wavelength # calibration csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) crval1_linear = poly_crval1_linear(csu_bar_slit_center) cdelt1_linear = poly_cdelt1_linear(csu_bar_slit_center) expected_wvmin = crval1_linear - \ args_margin_npix * cdelt1_linear naxis1_linear = sp_median.shape[0] crvaln_linear = crval1_linear + \ (naxis1_linear - 1) * cdelt1_linear expected_wvmax = crvaln_linear + \ args_margin_npix * cdelt1_linear # override previous estimates when necessary if wvmin_expected is not None: expected_wvmin = wvmin_expected if wvmax_expected is not None: expected_wvmax = wvmax_expected # clip initial master arc line list with bright lines to # the expected wavelength range lok1 = expected_wvmin <= wv_master lok2 = wv_master <= expected_wvmax lok = lok1 * lok2 wv_master_eff = wv_master[lok] # perform initial wavelength calibration solution_wv = wvcal_spectrum( sp=sp_median, fxpeaks=fxpeaks, poly_degree_wfit=args_poldeg_initial, wv_master=wv_master_eff, wv_ini_search=expected_wvmin, wv_end_search=expected_wvmax, wvmin_useful=wvmin_useful, wvmax_useful=wvmax_useful, geometry=args_geometry, debugplot=slt.debugplot) # store initial wavelength calibration polynomial in current # slitlet instance slt.wpoly = np.polynomial.Polynomial(solution_wv.coeff) pause_debugplot(debugplot) # clip initial master arc line list with all the lines to # the expected wavelength range lok1 = expected_wvmin <= wv_master_all lok2 = wv_master_all <= expected_wvmax lok = lok1 * lok2 wv_master_all_eff = wv_master_all[lok] # clip master arc line list to useful region if wvmin_useful is not None: lok = wvmin_useful <= wv_master_all_eff wv_master_all_eff = wv_master_all_eff[lok] if wvmax_useful is not None: lok = wv_master_all_eff <= wvmax_useful wv_master_all_eff = wv_master_all_eff[lok] # refine wavelength calibration if args_poldeg_refined > 0: plottitle = '[slitlet#{}, refined]'.format(islitlet) poly_refined, yres_summary = refine_arccalibration( sp=sp_median, poly_initial=slt.wpoly, wv_master=wv_master_all_eff, poldeg=args_poldeg_refined, ntimes_match_wv=1, interactive=args_interactive, threshold=args_threshold_wv, plottitle=plottitle, ylogscale=args_ylogscale, geometry=args_geometry, pdf=args_pdf, debugplot=slt.debugplot) # store refined wavelength calibration polynomial in # current slitlet instance slt.wpoly = poly_refined # compute approximate linear values for CRVAL1 and CDELT1 naxis1_linear = sp_median.shape[0] crmin1_linear = slt.wpoly(1) crmax1_linear = slt.wpoly(naxis1_linear) slt.crval1_linear = crmin1_linear slt.cdelt1_linear = \ (crmax1_linear - crmin1_linear) / (naxis1_linear - 1) # check that the trimming of wv_master and wv_master_all has # preserved the wavelength range [crmin1_linear, crmax1_linear] if crmin1_linear < expected_wvmin: logger.warning(">>> islitlet: " + str(islitlet)) logger.warning("expected_wvmin: " + str(expected_wvmin)) logger.warning("crmin1_linear.: " + str(crmin1_linear)) logger.warning("WARNING: Unexpected crmin1_linear < " "expected_wvmin") if crmax1_linear > expected_wvmax: logger.warning(">>> islitlet: " + str(islitlet)) logger.warning("expected_wvmax: " + str(expected_wvmax)) logger.warning("crmax1_linear.: " + str(crmax1_linear)) logger.warning("WARNING: Unexpected crmax1_linear > " "expected_wvmax") cout += '.' else: cout += 'x' if islitlet % 10 == 0: if cout != 'x': cout = str(islitlet // 10) if debugplot != 0: pause_debugplot(debugplot) else: # define Slitlet2dArc object slt = Slitlet2dArc(islitlet=islitlet, csu_conf=csu_conf, ymargin_bb=args_ymargin_bb, params=None, parmodel=None, debugplot=debugplot) cout += 'i' # store current slitlet in list of measured slitlets measured_slitlets.append(slt) logger.info(cout) # --- # generate FITS file structure with 55 spectra corresponding to the # median spectrum for each slitlet reduced_55sp = fits.PrimaryHDU(data=image2d_55sp) reduced_55sp.header['crpix1'] = (0.0, 'reference pixel') reduced_55sp.header['crval1'] = (0.0, 'central value at crpix2') reduced_55sp.header['cdelt1'] = (1.0, 'increment') reduced_55sp.header['ctype1'] = 'PIXEL' reduced_55sp.header['cunit1'] = ('Pixel', 'units along axis2') reduced_55sp.header['crpix2'] = (0.0, 'reference pixel') reduced_55sp.header['crval2'] = (0.0, 'central value at crpix2') reduced_55sp.header['cdelt2'] = (1.0, 'increment') reduced_55sp.header['ctype2'] = 'PIXEL' reduced_55sp.header['cunit2'] = ('Pixel', 'units along axis2') # --- # Generate structure to store intermediate results outdict = {} outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'computation of rectification and wavelength calibration polynomial ' \ 'coefficients for a particular CSU configuration' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['bound_param_uuid'] = \ bound_param.uuid outdict['meta_info']['origin']['arc_image_uuid'] = 'undefined' outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['tags']['islitlet_min'] = islitlet_min outdict['tags']['islitlet_max'] = islitlet_max outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} missing_slitlets = [] for slt in measured_slitlets: islitlet = slt.islitlet if islitlet_min <= islitlet <= islitlet_max: # avoid error when creating a python list of coefficients from # numpy polynomials when the polynomials do not exist (note that # the JSON format doesn't handle numpy arrays and such arrays must # be transformed into native python lists) if slt.wpoly is None: wpoly_coeff = None else: wpoly_coeff = slt.wpoly.coef.tolist() if slt.wpoly_longslit_model is None: wpoly_coeff_longslit_model = None else: wpoly_coeff_longslit_model = \ slt.wpoly_longslit_model.coef.tolist() # avoid similar error when creating a python list of coefficients # when the numpy array does not exist; note that this problem # does not happen with tt?_aij_longslit_model and # tt?_bij_longslit_model because the latter have already been # created as native python lists if slt.ttd_aij is None: ttd_aij = None else: ttd_aij = slt.ttd_aij.tolist() if slt.ttd_bij is None: ttd_bij = None else: ttd_bij = slt.ttd_bij.tolist() if slt.tti_aij is None: tti_aij = None else: tti_aij = slt.tti_aij.tolist() if slt.tti_bij is None: tti_bij = None else: tti_bij = slt.tti_bij.tolist() # creating temporary dictionary with the information corresponding # to the current slitlett that will be saved in the JSON file tmp_dict = { 'csu_bar_left': slt.csu_bar_left, 'csu_bar_right': slt.csu_bar_right, 'csu_bar_slit_center': slt.csu_bar_slit_center, 'csu_bar_slit_width': slt.csu_bar_slit_width, 'x0_reference': slt.x0_reference, 'y0_reference_lower': slt.y0_reference_lower, 'y0_reference_middle': slt.y0_reference_middle, 'y0_reference_upper': slt.y0_reference_upper, 'y0_reference_lower_expected': slt.y0_reference_lower_expected, 'y0_reference_middle_expected': slt.y0_reference_middle_expected, 'y0_reference_upper_expected': slt.y0_reference_upper_expected, 'y0_frontier_lower': slt.y0_frontier_lower, 'y0_frontier_upper': slt.y0_frontier_upper, 'y0_frontier_lower_expected': slt.y0_frontier_lower_expected, 'y0_frontier_upper_expected': slt.y0_frontier_upper_expected, 'corr_yrect_a': slt.corr_yrect_a, 'corr_yrect_b': slt.corr_yrect_b, 'min_row_rectified': slt.min_row_rectified, 'max_row_rectified': slt.max_row_rectified, 'ymargin_bb': slt.ymargin_bb, 'bb_nc1_orig': slt.bb_nc1_orig, 'bb_nc2_orig': slt.bb_nc2_orig, 'bb_ns1_orig': slt.bb_ns1_orig, 'bb_ns2_orig': slt.bb_ns2_orig, 'spectrail': { 'poly_coef_lower': slt.list_spectrails[ slt.i_lower_spectrail].poly_funct.coef.tolist(), 'poly_coef_middle': slt.list_spectrails[ slt.i_middle_spectrail].poly_funct.coef.tolist(), 'poly_coef_upper': slt.list_spectrails[ slt.i_upper_spectrail].poly_funct.coef.tolist(), }, 'frontier': { 'poly_coef_lower': slt.list_frontiers[0].poly_funct.coef.tolist(), 'poly_coef_upper': slt.list_frontiers[1].poly_funct.coef.tolist(), }, 'ttd_order': slt.ttd_order, 'ttd_aij': ttd_aij, 'ttd_bij': ttd_bij, 'tti_aij': tti_aij, 'tti_bij': tti_bij, 'ttd_order_longslit_model': slt.ttd_order_longslit_model, 'ttd_aij_longslit_model': slt.ttd_aij_longslit_model, 'ttd_bij_longslit_model': slt.ttd_bij_longslit_model, 'tti_aij_longslit_model': slt.tti_aij_longslit_model, 'tti_bij_longslit_model': slt.tti_bij_longslit_model, 'wpoly_coeff': wpoly_coeff, 'wpoly_coeff_longslit_model': wpoly_coeff_longslit_model, 'crval1_linear': slt.crval1_linear, 'cdelt1_linear': slt.cdelt1_linear } else: missing_slitlets.append(islitlet) tmp_dict = { 'csu_bar_left': slt.csu_bar_left, 'csu_bar_right': slt.csu_bar_right, 'csu_bar_slit_center': slt.csu_bar_slit_center, 'csu_bar_slit_width': slt.csu_bar_slit_width, 'x0_reference': slt.x0_reference, 'y0_frontier_lower_expected': slt.y0_frontier_lower_expected, 'y0_frontier_upper_expected': slt.y0_frontier_upper_expected } slitlet_label = "slitlet" + str(islitlet).zfill(2) outdict['contents'][slitlet_label] = tmp_dict # --- # OBSOLETE ''' # save JSON file needed to compute the MOS model with open(args.out_json.name, 'w') as fstream: json.dump(outdict, fstream, indent=2, sort_keys=True) print('>>> Saving file ' + args.out_json.name) ''' # --- # Create object of type RectWaveCoeff with coefficients for # rectification and wavelength calibration rectwv_coeff = RectWaveCoeff(instrument='EMIR') rectwv_coeff.quality_control = numina.types.qc.QC.GOOD rectwv_coeff.tags['grism'] = grism_name rectwv_coeff.tags['filter'] = filter_name rectwv_coeff.meta_info['origin']['bound_param'] = \ 'uuid' + bound_param.uuid rectwv_coeff.meta_info['dtu_configuration'] = outdict['dtu_configuration'] rectwv_coeff.total_slitlets = EMIR_NBARS rectwv_coeff.missing_slitlets = missing_slitlets for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: raise ValueError("Unexpected error") rectwv_coeff.contents.append(dumdict) # debugging __getstate__ and __setstate__ # rectwv_coeff.writeto(args.out_json.name) # print('>>> Saving file ' + args.out_json.name) # check_setstate_getstate(rectwv_coeff, args.out_json.name) logger.info('Generating RectWaveCoeff object with uuid=' + rectwv_coeff.uuid) return rectwv_coeff, reduced_55sp
def rectwv_coeff_from_mos_library(reduced_image, master_rectwv, ignore_dtu_configuration=True, debugplot=0): """Evaluate rect.+wavecal. coefficients from MOS library Parameters ---------- reduced_image : HDUList object Image with preliminary basic reduction: bpm, bias, dark and flatfield. master_rectwv : MasterRectWave instance Rectification and Wavelength Calibrartion Library product. Contains the library of polynomial coefficients necessary to generate an instance of RectWaveCoeff with the rectification and wavelength calibration coefficients for the particular CSU configuration. ignore_dtu_configuration : bool If True, ignore differences in DTU configuration. debugplot : int Debugging level for messages and plots. For details see 'numina.array.display.pause_debugplot.py'. Returns ------- rectwv_coeff : RectWaveCoeff instance Rectification and wavelength calibration coefficients for the particular CSU configuration. """ logger = logging.getLogger(__name__) logger.info('Computing expected RectWaveCoeff from CSU configuration') # header header = reduced_image[0].header # read the CSU configuration from the image header csu_conf = CsuConfiguration.define_from_header(header) # read the DTU configuration from the image header dtu_conf = DtuConfiguration.define_from_header(header) # retrieve DTU configuration from MasterRectWave object dtu_conf_calib = DtuConfiguration.define_from_dictionary( master_rectwv.meta_info['dtu_configuration'] ) # check that the DTU configuration employed to obtain the calibration # corresponds to the DTU configuration in the input FITS file if dtu_conf != dtu_conf_calib: if ignore_dtu_configuration: logger.warning('DTU configuration differences found!') else: logger.info('DTU configuration from image header:') logger.info(dtu_conf) logger.info('DTU configuration from master calibration:') logger.info(dtu_conf_calib) raise ValueError("DTU configurations do not match!") else: logger.info('DTU configuration match!') # check grism and filter filter_name = header['filter'] logger.debug('Filter: ' + filter_name) if filter_name != master_rectwv.tags['filter']: raise ValueError('Filter name does not match!') grism_name = header['grism'] logger.debug('Grism: ' + grism_name) if grism_name != master_rectwv.tags['grism']: raise ValueError('Grism name does not match!') # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in master_rectwv.missing_slitlets: list_valid_islitlets.remove(idel) logger.debug('valid slitlet numbers: ' + str(list_valid_islitlets)) # initialize intermediate dictionary with relevant information # (note: this dictionary corresponds to an old structure employed to # store the information in a JSON file; this is no longer necessary, # but here we reuse that dictionary for convenience) outdict = {} outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'computation of rectification and wavelength calibration polynomial ' \ 'coefficients for a particular CSU configuration from a MOS model ' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['fits_frame_uuid'] = 'TBD' outdict['meta_info']['origin']['rect_wpoly_mos_uuid'] = \ master_rectwv.uuid outdict['meta_info']['origin']['fitted_boundary_param_uuid'] = \ master_rectwv.meta_info['origin']['bound_param'] outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} # compute rectification and wavelength calibration coefficients for each # slitlet according to its csu_bar_slit_center value for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # csu_bar_slit_center of current slitlet in initial FITS image csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) # input data structure tmpdict = master_rectwv.contents[islitlet - 1] list_csu_bar_slit_center = tmpdict['list_csu_bar_slit_center'] # check extrapolations if csu_bar_slit_center < min(list_csu_bar_slit_center): logger.warning('extrapolating table with ' + cslitlet) logger.warning('minimum tabulated value: ' + str(min(list_csu_bar_slit_center))) logger.warning('sought value...........: ' + str(csu_bar_slit_center)) if csu_bar_slit_center > max(list_csu_bar_slit_center): logger.warning('extrapolating table with ' + cslitlet) logger.warning('maximum tabulated value: ' + str(max(list_csu_bar_slit_center))) logger.warning('sought value...........: ' + str(csu_bar_slit_center)) # rectification coefficients ttd_order = tmpdict['ttd_order'] ncoef = ncoef_fmap(ttd_order) outdict['contents'][cslitlet] = {} outdict['contents'][cslitlet]['ttd_order'] = ttd_order outdict['contents'][cslitlet]['ttd_order_longslit_model'] = None for keycoef in ['ttd_aij', 'ttd_bij', 'tti_aij', 'tti_bij']: coef_out = [] for icoef in range(ncoef): ccoef = str(icoef).zfill(2) list_cij = tmpdict['list_' + keycoef + '_' + ccoef] funinterp_coef = interp1d(list_csu_bar_slit_center, list_cij, kind='linear', fill_value='extrapolate') # note: funinterp_coef expects a numpy array dum = funinterp_coef([csu_bar_slit_center]) coef_out.append(dum[0]) outdict['contents'][cslitlet][keycoef] = coef_out outdict['contents'][cslitlet][keycoef + '_longslit_model'] = None # wavelength calibration coefficients ncoef = tmpdict['wpoly_degree'] + 1 wpoly_coeff = [] for icoef in range(ncoef): ccoef = str(icoef).zfill(2) list_cij = tmpdict['list_wpoly_coeff_' + ccoef] funinterp_coef = interp1d(list_csu_bar_slit_center, list_cij, kind='linear', fill_value='extrapolate') # note: funinterp_coef expects a numpy array dum = funinterp_coef([csu_bar_slit_center]) wpoly_coeff.append(dum[0]) outdict['contents'][cslitlet]['wpoly_coeff'] = wpoly_coeff outdict['contents'][cslitlet]['wpoly_coeff_longslit_model'] = None # update cdelt1_linear and crval1_linear wpoly_function = np.polynomial.Polynomial(wpoly_coeff) crmin1_linear = wpoly_function(1) crmax1_linear = wpoly_function(EMIR_NAXIS1) cdelt1_linear = (crmax1_linear - crmin1_linear) / (EMIR_NAXIS1 - 1) crval1_linear = crmin1_linear outdict['contents'][cslitlet]['crval1_linear'] = crval1_linear outdict['contents'][cslitlet]['cdelt1_linear'] = cdelt1_linear # update CSU keywords outdict['contents'][cslitlet]['csu_bar_left'] = \ csu_conf.csu_bar_left(islitlet) outdict['contents'][cslitlet]['csu_bar_right'] = \ csu_conf.csu_bar_right(islitlet) outdict['contents'][cslitlet]['csu_bar_slit_center'] = \ csu_conf.csu_bar_slit_center(islitlet) outdict['contents'][cslitlet]['csu_bar_slit_width'] = \ csu_conf.csu_bar_slit_width(islitlet) # for each slitlet compute spectrum trails and frontiers using the # fitted boundary parameters fitted_bound_param_json = { 'contents': master_rectwv.meta_info['refined_boundary_model'] } parmodel = fitted_bound_param_json['contents']['parmodel'] fitted_bound_param_json.update({'meta_info': {'parmodel': parmodel}}) params = bound_params_from_dict(fitted_bound_param_json) if abs(debugplot) >= 10: logger.debug('Fitted boundary parameters:') logger.debug(params.pretty_print()) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # csu_bar_slit_center of current slitlet in initial FITS image csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) # compute and store x0_reference value x0_reference = float(EMIR_NAXIS1) / 2.0 + 0.5 outdict['contents'][cslitlet]['x0_reference'] = x0_reference # compute spectrum trails (lower, middle and upper) list_spectrails = expected_distorted_boundaries( islitlet, csu_bar_slit_center, [0, 0.5, 1], params, parmodel, numpts=101, deg=5, debugplot=0 ) # store spectrails in output JSON file outdict['contents'][cslitlet]['spectrail'] = {} for idum, cdum in zip(range(3), ['lower', 'middle', 'upper']): outdict['contents'][cslitlet]['spectrail']['poly_coef_' + cdum] = \ list_spectrails[idum].poly_funct.coef.tolist() outdict['contents'][cslitlet]['y0_reference_' + cdum] = \ list_spectrails[idum].poly_funct(x0_reference) # compute frontiers (lower, upper) list_frontiers = expected_distorted_frontiers( islitlet, csu_bar_slit_center, params, parmodel, numpts=101, deg=5, debugplot=0 ) # store frontiers in output JSON outdict['contents'][cslitlet]['frontier'] = {} for idum, cdum in zip(range(2), ['lower', 'upper']): outdict['contents'][cslitlet]['frontier']['poly_coef_' + cdum] = \ list_frontiers[idum].poly_funct.coef.tolist() outdict['contents'][cslitlet]['y0_frontier_' + cdum] = \ list_frontiers[idum].poly_funct(x0_reference) # store bounding box parameters for each slitlet xdum = np.linspace(1, EMIR_NAXIS1, num=EMIR_NAXIS1) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # parameters already available in the input JSON file for par in ['bb_nc1_orig', 'bb_nc2_orig', 'ymargin_bb']: outdict['contents'][cslitlet][par] = \ master_rectwv.contents[islitlet - 1][par] # estimate bb_ns1_orig and bb_ns2_orig using the already computed # frontiers and the value of ymargin_bb, following the same approach # employed in Slitlet2dArc.__init__() poly_lower_frontier = np.polynomial.Polynomial( outdict['contents'][cslitlet]['frontier']['poly_coef_lower'] ) poly_upper_frontier = np.polynomial.Polynomial( outdict['contents'][cslitlet]['frontier']['poly_coef_upper'] ) ylower = poly_lower_frontier(xdum) yupper = poly_upper_frontier(xdum) ymargin_bb = master_rectwv.contents[islitlet - 1]['ymargin_bb'] bb_ns1_orig = int(ylower.min() + 0.5) - ymargin_bb if bb_ns1_orig < 1: bb_ns1_orig = 1 bb_ns2_orig = int(yupper.max() + 0.5) + ymargin_bb if bb_ns2_orig > EMIR_NAXIS2: bb_ns2_orig = EMIR_NAXIS2 outdict['contents'][cslitlet]['bb_ns1_orig'] = bb_ns1_orig outdict['contents'][cslitlet]['bb_ns2_orig'] = bb_ns2_orig # additional parameters (see Slitlet2dArc.__init__) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # define expected frontier ordinates at x0_reference for the rectified # image imposing the vertical length of the slitlet to be constant # and equal to EMIR_NPIXPERSLIT_RECTIFIED outdict['contents'][cslitlet]['y0_frontier_lower_expected'] = \ expected_y0_lower_frontier(islitlet) outdict['contents'][cslitlet]['y0_frontier_upper_expected'] = \ expected_y0_upper_frontier(islitlet) # compute linear transformation to place the rectified slitlet at # the center of the current slitlet bounding box tmpdict = outdict['contents'][cslitlet] xdum1 = tmpdict['y0_frontier_lower'] ydum1 = tmpdict['y0_frontier_lower_expected'] xdum2 = tmpdict['y0_frontier_upper'] ydum2 = tmpdict['y0_frontier_upper_expected'] corr_yrect_b = (ydum2 - ydum1) / (xdum2 - xdum1) corr_yrect_a = ydum1 - corr_yrect_b * xdum1 # compute expected location of rectified boundaries y0_reference_lower_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_lower'] y0_reference_middle_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_middle'] y0_reference_upper_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_upper'] # shift transformation to center the rectified slitlet within the # slitlet bounding box ydummid = (ydum1 + ydum2) / 2 ioffset = int( ydummid - (tmpdict['bb_ns1_orig'] + tmpdict['bb_ns2_orig']) / 2.0) corr_yrect_a -= ioffset # minimum and maximum row in the rectified slitlet encompassing # EMIR_NPIXPERSLIT_RECTIFIED pixels # a) scan number (in pixels, from 1 to NAXIS2) xdum1 = corr_yrect_a + \ corr_yrect_b * tmpdict['y0_frontier_lower'] xdum2 = corr_yrect_a + \ corr_yrect_b * tmpdict['y0_frontier_upper'] # b) row number (starting from zero) min_row_rectified = \ int((round(xdum1 * 10) + 5) / 10) - tmpdict['bb_ns1_orig'] max_row_rectified = \ int((round(xdum2 * 10) - 5) / 10) - tmpdict['bb_ns1_orig'] # save previous results in outdict outdict['contents'][cslitlet]['y0_reference_lower_expected'] = \ y0_reference_lower_expected outdict['contents'][cslitlet]['y0_reference_middle_expected'] = \ y0_reference_middle_expected outdict['contents'][cslitlet]['y0_reference_upper_expected'] = \ y0_reference_upper_expected outdict['contents'][cslitlet]['corr_yrect_a'] = corr_yrect_a outdict['contents'][cslitlet]['corr_yrect_b'] = corr_yrect_b outdict['contents'][cslitlet]['min_row_rectified'] = min_row_rectified outdict['contents'][cslitlet]['max_row_rectified'] = max_row_rectified # --- # Create object of type RectWaveCoeff with coefficients for # rectification and wavelength calibration rectwv_coeff = RectWaveCoeff(instrument='EMIR') rectwv_coeff.quality_control = numina.types.qc.QC.GOOD rectwv_coeff.tags['grism'] = grism_name rectwv_coeff.tags['filter'] = filter_name rectwv_coeff.meta_info['origin']['bound_param'] = \ master_rectwv.meta_info['origin']['bound_param'] rectwv_coeff.meta_info['origin']['master_rectwv'] = \ 'uuid' + master_rectwv.uuid rectwv_coeff.meta_info['dtu_configuration'] = outdict['dtu_configuration'] rectwv_coeff.total_slitlets = EMIR_NBARS for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: dumdict.update({ 'csu_bar_left': csu_conf.csu_bar_left(islitlet), 'csu_bar_right': csu_conf.csu_bar_right(islitlet), 'csu_bar_slit_center': csu_conf.csu_bar_slit_center(islitlet), 'csu_bar_slit_width': csu_conf.csu_bar_slit_width(islitlet), 'x0_reference': float(EMIR_NAXIS1) / 2.0 + 0.5, 'y0_frontier_lower_expected': expected_y0_lower_frontier(islitlet), 'y0_frontier_upper_expected': expected_y0_upper_frontier(islitlet) }) rectwv_coeff.missing_slitlets.append(islitlet) rectwv_coeff.contents.append(dumdict) # debugging __getstate__ and __setstate__ # rectwv_coeff.writeto(args.out_rect_wpoly.name) # print('>>> Saving file ' + args.out_rect_wpoly.name) # check_setstate_getstate(rectwv_coeff, args.out_rect_wpoly.name) logger.info('Generating RectWaveCoeff object with uuid=' + rectwv_coeff.uuid) return rectwv_coeff
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: compute pixel-to-pixel flatfield') # required arguments parser.add_argument("fitsfile", help="Input FITS file (flat ON-OFF)", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--minimum_fraction", required=True, help="Minimum allowed flatfielding value", type=float, default=0.01) parser.add_argument("--minimum_value_in_output", help="Minimum value allowed in output file: pixels " "below this value are set to 1.0 (default=0.01)", type=float, default=0.01) parser.add_argument("--nwindow_median", required=True, help="Window size to smooth median spectrum in the " "spectral direction", type=int) parser.add_argument("--outfile", required=True, help="Output FITS file", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_DTUconf", help="Ignore DTU configurations differences between " "model and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # read calibration structure from JSON file rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) header = hdulist[0].header image2d = hdulist[0].data hdulist.close() # apply global offsets image2d = apply_integer_offsets( image2d=image2d, offx=rectwv_coeff.global_integer_offset_x_pix, offy=rectwv_coeff.global_integer_offset_y_pix) # protections naxis2, naxis1 = image2d.shape if naxis1 != header['naxis1'] or naxis2 != header['naxis2']: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) raise ValueError('Something is wrong with NAXIS1 and/or NAXIS2') if abs(args.debugplot) >= 10: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) # check that the input FITS file grism and filter match filter_name = header['filter'] if filter_name != rectwv_coeff.tags['filter']: raise ValueError("Filter name does not match!") grism_name = header['grism'] if grism_name != rectwv_coeff.tags['grism']: raise ValueError("Filter name does not match!") if abs(args.debugplot) >= 10: print('>>> grism.......:', grism_name) print('>>> filter......:', filter_name) # check that the DTU configurations are compatible dtu_conf_fitsfile = DtuConfiguration.define_from_fits(args.fitsfile) dtu_conf_jsonfile = DtuConfiguration.define_from_dictionary( rectwv_coeff.meta_info['dtu_configuration']) if dtu_conf_fitsfile != dtu_conf_jsonfile: print('DTU configuration (FITS file):\n\t', dtu_conf_fitsfile) print('DTU configuration (JSON file):\n\t', dtu_conf_jsonfile) if args.ignore_DTUconf: print('WARNING: DTU configuration differences found!') else: raise ValueError('DTU configurations do not match') else: if abs(args.debugplot) >= 10: print('>>> DTU Configuration match!') print(dtu_conf_fitsfile) # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in rectwv_coeff.missing_slitlets: list_valid_islitlets.remove(idel) if abs(args.debugplot) >= 10: print('>>> valid slitlet numbers:\n', list_valid_islitlets) # --- # initialize rectified image image2d_flatfielded = np.zeros((EMIR_NAXIS2, EMIR_NAXIS1)) # main loop for islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) if abs(args.debugplot) >= 10: print(slt) # extract (distorted) slitlet from the initial image slitlet2d = slt.extract_slitlet2d(image2d) # rectify slitlet slitlet2d_rect = slt.rectify(slitlet2d, resampling=args.resampling) naxis2_slitlet2d, naxis1_slitlet2d = slitlet2d_rect.shape if naxis1_slitlet2d != EMIR_NAXIS1: print('naxis1_slitlet2d: ', naxis1_slitlet2d) print('EMIR_NAXIS1.....: ', EMIR_NAXIS1) raise ValueError("Unexpected naxis1_slitlet2d") # get useful slitlet region (use boundaires instead of frontiers; # note that the nscan_minmax_frontiers() works well independently # of using frontiers of boundaries as arguments) nscan_min, nscan_max = nscan_minmax_frontiers(slt.y0_reference_lower, slt.y0_reference_upper, resize=False) ii1 = nscan_min - slt.bb_ns1_orig ii2 = nscan_max - slt.bb_ns1_orig + 1 # median spectrum sp_collapsed = np.median(slitlet2d_rect[ii1:(ii2 + 1), :], axis=0) # smooth median spectrum along the spectral direction sp_median = ndimage.median_filter(sp_collapsed, args.nwindow_median, mode='nearest') ymax_spmedian = sp_median.max() y_threshold = ymax_spmedian * args.minimum_fraction sp_median[np.where(sp_median < y_threshold)] = 0.0 if abs(args.debugplot) > 10: title = 'Slitlet#' + str(islitlet) + '(median spectrum)' xdum = np.arange(1, naxis1_slitlet2d + 1) ax = ximplotxy(xdum, sp_collapsed, title=title, show=False, **{'label': 'collapsed spectrum'}) ax.plot(xdum, sp_median, label='filtered spectrum') ax.plot([1, naxis1_slitlet2d], 2 * [y_threshold], label='threshold') ax.legend() ax.set_ylim(-0.05 * ymax_spmedian, 1.05 * ymax_spmedian) pause_debugplot(args.debugplot, pltshow=True, tight_layout=True) # generate rectified slitlet region filled with the median spectrum slitlet2d_rect_spmedian = np.tile(sp_median, (naxis2_slitlet2d, 1)) if abs(args.debugplot) > 10: slt.ximshow_rectified(slitlet2d_rect_spmedian) # unrectified image slitlet2d_unrect_spmedian = slt.rectify(slitlet2d_rect_spmedian, resampling=args.resampling, inverse=True) # normalize initial slitlet image (avoid division by zero) slitlet2d_norm = np.zeros_like(slitlet2d) for j in range(naxis1_slitlet2d): for i in range(naxis2_slitlet2d): den = slitlet2d_unrect_spmedian[i, j] if den == 0: slitlet2d_norm[i, j] = 1.0 else: slitlet2d_norm[i, j] = slitlet2d[i, j] / den if abs(args.debugplot) > 10: slt.ximshow_unrectified(slitlet2d_norm) for j in range(EMIR_NAXIS1): xchannel = j + 1 y0_lower = slt.list_frontiers[0](xchannel) y0_upper = slt.list_frontiers[1](xchannel) n1, n2 = nscan_minmax_frontiers(y0_frontier_lower=y0_lower, y0_frontier_upper=y0_upper, resize=True) # note that n1 and n2 are scans (ranging from 1 to NAXIS2) nn1 = n1 - slt.bb_ns1_orig + 1 nn2 = n2 - slt.bb_ns1_orig + 1 image2d_flatfielded[(n1 - 1):n2, j] = \ slitlet2d_norm[(nn1 - 1):nn2, j] # force to 1.0 region around frontiers image2d_flatfielded[(n1 - 1):(n1 + 2), j] = 1 image2d_flatfielded[(n2 - 5):n2, j] = 1 if args.debugplot == 0: print('OK!') # set pixels below minimum value to 1.0 filtered = np.where(image2d_flatfielded < args.minimum_value_in_output) image2d_flatfielded[filtered] = 1.0 # restore global offsets image2d_flatfielded = apply_integer_offsets( image2d=image2d_flatfielded, offx=-rectwv_coeff.global_integer_offset_x_pix, offy=-rectwv_coeff.global_integer_offset_y_pix) # save output file save_ndarray_to_fits(array=image2d_flatfielded, file_name=args.outfile, main_header=header, overwrite=True) print('>>> Saving file ' + args.outfile.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser(prog='rect_wpoly_for_mos') # required arguments parser.add_argument("input_list", help="TXT file with list JSON files derived from " "longslit data") parser.add_argument("--fitted_bound_param", required=True, help="Input JSON with fitted boundary parameters", type=argparse.FileType('rt')) parser.add_argument("--out_MOSlibrary", required=True, help="Output JSON file with results", type=lambda x: arg_file_is_new(parser, x)) # optional arguments parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- # Read input TXT file with list of JSON files list_json_files = list_fileinfo_from_txt(args.input_list) nfiles = len(list_json_files) if abs(args.debugplot) >= 10: print('>>> Number of input JSON files:', nfiles) for item in list_json_files: print(item) if nfiles < 2: raise ValueError("Insufficient number of input JSON files") # read fitted boundary parameters and check that all the longslit JSON # files have been computed using the same fitted boundary parameters refined_boundary_model = RefinedBoundaryModelParam._datatype_load( args.fitted_bound_param.name) for ifile in range(nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) uuid_tmp = coef_rect_wpoly.meta_info['origin']['bound_param'] if uuid_tmp[4:] != refined_boundary_model.uuid: print('Expected uuid:', refined_boundary_model.uuid) print('uuid for ifile #' + str(ifile + 1) + ": " + uuid_tmp) raise ValueError("Fitted boundary parameter uuid's do not match") # check consistency of grism, filter, DTU configuration and list of # valid slitlets coef_rect_wpoly_first_longslit = RectWaveCoeff._datatype_load( list_json_files[0].filename) filter_name = coef_rect_wpoly_first_longslit.tags['filter'] grism_name = coef_rect_wpoly_first_longslit.tags['grism'] dtu_conf = DtuConfiguration.define_from_dictionary( coef_rect_wpoly_first_longslit.meta_info['dtu_configuration'] ) list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in coef_rect_wpoly_first_longslit.missing_slitlets: list_valid_islitlets.remove(idel) for ifile in range(1, nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) filter_tmp = coef_rect_wpoly.tags['filter'] if filter_name != filter_tmp: print(filter_name) print(filter_tmp) raise ValueError("Unexpected different filter found") grism_tmp = coef_rect_wpoly.tags['grism'] if grism_name != grism_tmp: print(grism_name) print(grism_tmp) raise ValueError("Unexpected different grism found") coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) dtu_conf_tmp = DtuConfiguration.define_from_dictionary( coef_rect_wpoly.meta_info['dtu_configuration'] ) if dtu_conf != dtu_conf_tmp: print(dtu_conf) print(dtu_conf_tmp) raise ValueError("Unexpected different DTU configurations found") list_valid_islitlets_tmp = list(range(1, EMIR_NBARS + 1)) for idel in coef_rect_wpoly.missing_slitlets: list_valid_islitlets_tmp.remove(idel) if list_valid_islitlets != list_valid_islitlets_tmp: print(list_valid_islitlets) print(list_valid_islitlets_tmp) raise ValueError("Unexpected different list of valid slitlets") # check consistency of horizontal bounding box limits (bb_nc1_orig and # bb_nc2_orig) and ymargin_bb, and store the values for each slitlet dict_bb_param = {} print("Checking horizontal bounding box limits and ymargin_bb:") for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) cslitlet = 'slitlet' + str(islitlet).zfill(2) dict_bb_param[cslitlet] = {} for par in ['bb_nc1_orig', 'bb_nc2_orig', 'ymargin_bb']: value_initial = \ coef_rect_wpoly_first_longslit.contents[islitlet - 1][par] for ifile in range(1, nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) value_tmp = coef_rect_wpoly.contents[islitlet - 1][par] if value_initial != value_tmp: print(islitlet, value_initial, value_tmp) print(value_tmp) raise ValueError("Unexpected different " + par) dict_bb_param[cslitlet][par] = value_initial else: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) print('OK!') # --- # Read and store all the longslit data list_coef_rect_wpoly = [] for ifile in range(nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) list_coef_rect_wpoly.append(coef_rect_wpoly) # --- # Initialize structure to save results into an ouptut JSON file outdict = {} outdict['refined_boundary_model'] = refined_boundary_model.__getstate__() outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'rectification and wavelength calibration polynomial coefficients ' \ 'as a function of csu_bar_slit_center for MOS' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['wpoly_longslits'] = {} for ifile in range(nfiles): cdum = 'longslit_' + str(ifile + 1).zfill(3) + '_uuid' outdict['meta_info']['origin']['wpoly_longslits'][cdum] = \ list_coef_rect_wpoly[ifile].uuid outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} # include bb_nc1_orig, bb_nc2_orig and ymargin_bb for each slitlet # (note that the values of bb_ns1_orig and bb_ns2_orig cannot be # computed at this stage because they depend on csu_bar_slit_center) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet] = dict_bb_param[cslitlet] # check that order for rectification transformations is the same for all # the slitlets and longslit configurations order_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: ttd_order = tmpdict[islitlet - 1]['ttd_order'] if ttd_order is not None: order_check_list.append(ttd_order) ttd_order_modeled = \ tmpdict[islitlet - 1]['ttd_order_longslit_model'] order_check_list.append(ttd_order_modeled) # remove duplicates in list order_no_duplicates = list(set(order_check_list)) if len(order_no_duplicates) != 1: print('order_no_duplicates:', order_no_duplicates) raise ValueError('tdd_order is not constant!') ttd_order = int(order_no_duplicates[0]) ncoef_rect = ncoef_fmap(ttd_order) if abs(args.debugplot) >= 10: print('>>> ttd_order........:', ttd_order) print('>>> ncoef_rect.......:', ncoef_rect) # check that polynomial degree in frontiers and spectrails are the same poldeg_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: tmppoly = tmpdict[islitlet - 1]['frontier']['poly_coef_lower'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['frontier']['poly_coef_upper'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_lower'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_middle'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_upper'] poldeg_check_list.append(len(tmppoly) - 1) # remove duplicates in list poldeg_no_duplicates = list(set(poldeg_check_list)) if len(poldeg_no_duplicates) != 1: print('poldeg_no_duplicates:', poldeg_no_duplicates) raise ValueError('poldeg is not constant in frontiers and ' 'spectrails!') poldeg_spectrails = int(poldeg_no_duplicates[0]) if abs(args.debugplot) >= 10: print('>>> poldeg spectrails:', poldeg_spectrails) # check that polynomial degree of wavelength calibration is the same for # all the slitlets poldeg_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: tmppoly = tmpdict[islitlet - 1]['wpoly_coeff'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['wpoly_coeff_longslit_model'] poldeg_check_list.append(len(tmppoly) - 1) # remove duplicates in list poldeg_no_duplicates = list(set(poldeg_check_list)) if len(poldeg_no_duplicates) != 1: print('poldeg_no_duplicates:', poldeg_no_duplicates) raise ValueError('poldeg is not constant in wavelength calibration ' 'polynomials!') poldeg_wavecal = int(poldeg_no_duplicates[0]) if abs(args.debugplot) >= 10: print('>>> poldeg wavecal...:', poldeg_wavecal) # --- # csu_bar_slit_center values for each slitlet print("CSU_bar_slit_center values:") for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) cslitlet = 'slitlet' + str(islitlet).zfill(2) list_csu_bar_slit_center = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents[islitlet - 1] csu_bar_slit_center = tmpdict['csu_bar_slit_center'] list_csu_bar_slit_center.append(csu_bar_slit_center) # check that list_csu_bar_slit_center is properly sorted if not np.all(list_csu_bar_slit_center[:-1] <= list_csu_bar_slit_center[1:]): print('cslitlet: ', cslitlet) print('list_csu_bar_slit_center: ', list_csu_bar_slit_center) raise ValueError('Unsorted list_csu_bar_slit_center') outdict['contents'][cslitlet]['list_csu_bar_slit_center'] = \ list_csu_bar_slit_center else: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) print('OK!') # --- # rectification polynomial coefficients # note: when aij and bij have not been computed, we use the modeled # version aij_longslit_model and bij_longslit_model print("Rectification polynomial coefficients:") for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet]['ttd_order'] = ttd_order outdict['contents'][cslitlet]['ncoef_rect'] = ncoef_rect for keycoef in ['ttd_aij', 'ttd_bij', 'tti_aij', 'tti_bij']: for icoef in range(ncoef_rect): ccoef = str(icoef).zfill(2) list_cij = [] for ifile in range(nfiles): tmpdict = \ list_coef_rect_wpoly[ifile].contents[islitlet - 1] cij = tmpdict[keycoef] if cij is not None: list_cij.append(cij[icoef]) else: cij_modeled = tmpdict[keycoef + '_longslit_model'] if cij_modeled is None: raise ValueError("Unexpected cij_modeled=None!") else: list_cij.append(cij_modeled[icoef]) if abs(args.debugplot) >= 10: print("Warning: using " + keycoef + "_longslit_model for " + cslitlet + " in file " + list_json_files[ifile].filename) cdum = 'list_' + keycoef + '_' + ccoef outdict['contents'][cslitlet][cdum] = list_cij else: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) print('OK!') # --- # wavelength calibration polynomial coefficients # note: when wpoly_coeff have not been computed, we use the # wpoly_coeff_longslit_model print("Wavelength calibration polynomial coefficients:") for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet]['wpoly_degree'] = poldeg_wavecal for icoef in range(poldeg_wavecal + 1): ccoef = str(icoef).zfill(2) list_cij = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents[islitlet - 1] cij = tmpdict['wpoly_coeff'] if cij is not None: list_cij.append(cij[icoef]) else: cij_modeled = tmpdict['wpoly_coeff_longslit_model'] if cij_modeled is None: raise ValueError("Unexpected cij_modeled=None!") else: list_cij.append(cij_modeled[icoef]) if abs(args.debugplot) >= 10: print("Warning: using wpoly_coeff_longslit_model" + " for " + cslitlet + " in file " + list_json_files[ifile].filename) outdict['contents'][cslitlet]['list_wpoly_coeff_' + ccoef] = \ list_cij else: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) print('OK!') # --- # OBSOLETE # Save resulting JSON structure ''' with open(args.out_MOSlibrary.name + '_old', 'w') as fstream: json.dump(outdict, fstream, indent=2, sort_keys=True) print('>>> Saving file ' + args.out_MOSlibrary.name + '_old') ''' # -- # Create object of type MasterRectWave with library of coefficients # for rectification and wavelength calibration master_rectwv = MasterRectWave(instrument='EMIR') master_rectwv.quality_control = numina.types.qc.QC.GOOD master_rectwv.tags['grism'] = grism_name master_rectwv.tags['filter'] = filter_name master_rectwv.meta_info['dtu_configuration'] = outdict['dtu_configuration'] master_rectwv.meta_info['refined_boundary_model'] = { 'parmodel': refined_boundary_model.meta_info['parmodel'] } master_rectwv.meta_info['refined_boundary_model'].update( outdict['refined_boundary_model']['contents'] ) master_rectwv.total_slitlets = EMIR_NBARS master_rectwv.meta_info['origin'] = { 'bound_param': 'uuid' + refined_boundary_model.uuid, 'longslit_frames': ['uuid:' + list_coef_rect_wpoly[ifile].uuid for ifile in range(nfiles)] } for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: dumdict.update({ 'bb_nc1_orig': 0, 'bb_nc2_orig': 0, 'ymargin_bb': 0, 'list_csu_bar_slit_center': [], 'ttd_order': 0, 'ncoef_rect': 0, 'wpolydegree': 0 }) master_rectwv.missing_slitlets.append(islitlet) master_rectwv.contents.append(dumdict) master_rectwv.writeto(args.out_MOSlibrary.name) print('>>> Saving file ' + args.out_MOSlibrary.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser(prog='rect_wpoly_for_mos') # required arguments parser.add_argument("input_list", help="TXT file with list JSON files derived from " "longslit data") parser.add_argument("--fitted_bound_param", required=True, help="Input JSON with fitted boundary parameters", type=argparse.FileType('rt')) parser.add_argument("--out_MOSlibrary", required=True, help="Output JSON file with results", type=lambda x: arg_file_is_new(parser, x)) # optional arguments parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- # Read input TXT file with list of JSON files list_json_files = list_fileinfo_from_txt(args.input_list) nfiles = len(list_json_files) if abs(args.debugplot) >= 10: print('>>> Number of input JSON files:', nfiles) for item in list_json_files: print(item) if nfiles < 2: raise ValueError("Insufficient number of input JSON files") # read fitted boundary parameters and check that all the longslit JSON # files have been computed using the same fitted boundary parameters refined_boundary_model = RefinedBoundaryModelParam._datatype_load( args.fitted_bound_param.name) for ifile in range(nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) uuid_tmp = coef_rect_wpoly.meta_info['origin']['bound_param'] if uuid_tmp[4:] != refined_boundary_model.uuid: print('Expected uuid:', refined_boundary_model.uuid) print('uuid for ifile #' + str(ifile + 1) + ": " + uuid_tmp) raise ValueError("Fitted boundary parameter uuid's do not match") # check consistency of grism, filter, DTU configuration and list of # valid slitlets coef_rect_wpoly_first_longslit = RectWaveCoeff._datatype_load( list_json_files[0].filename) filter_name = coef_rect_wpoly_first_longslit.tags['filter'] grism_name = coef_rect_wpoly_first_longslit.tags['grism'] dtu_conf = DtuConfiguration.define_from_dictionary( coef_rect_wpoly_first_longslit.meta_info['dtu_configuration']) list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in coef_rect_wpoly_first_longslit.missing_slitlets: list_valid_islitlets.remove(idel) for ifile in range(1, nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) filter_tmp = coef_rect_wpoly.tags['filter'] if filter_name != filter_tmp: print(filter_name) print(filter_tmp) raise ValueError("Unexpected different filter found") grism_tmp = coef_rect_wpoly.tags['grism'] if grism_name != grism_tmp: print(grism_name) print(grism_tmp) raise ValueError("Unexpected different grism found") coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) dtu_conf_tmp = DtuConfiguration.define_from_dictionary( coef_rect_wpoly.meta_info['dtu_configuration']) if dtu_conf != dtu_conf_tmp: print(dtu_conf) print(dtu_conf_tmp) raise ValueError("Unexpected different DTU configurations found") list_valid_islitlets_tmp = list(range(1, EMIR_NBARS + 1)) for idel in coef_rect_wpoly.missing_slitlets: list_valid_islitlets_tmp.remove(idel) if list_valid_islitlets != list_valid_islitlets_tmp: print(list_valid_islitlets) print(list_valid_islitlets_tmp) raise ValueError("Unexpected different list of valid slitlets") # check consistency of horizontal bounding box limits (bb_nc1_orig and # bb_nc2_orig) and ymargin_bb, and store the values for each slitlet dict_bb_param = {} print("Checking horizontal bounding box limits and ymargin_bb:") for islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS) cslitlet = 'slitlet' + str(islitlet).zfill(2) dict_bb_param[cslitlet] = {} for par in ['bb_nc1_orig', 'bb_nc2_orig', 'ymargin_bb']: value_initial = \ coef_rect_wpoly_first_longslit.contents[islitlet - 1][par] for ifile in range(1, nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) value_tmp = coef_rect_wpoly.contents[islitlet - 1][par] if value_initial != value_tmp: print(islitlet, value_initial, value_tmp) print(value_tmp) raise ValueError("Unexpected different " + par) dict_bb_param[cslitlet][par] = value_initial print('OK!') # --- # Read and store all the longslit data list_coef_rect_wpoly = [] for ifile in range(nfiles): coef_rect_wpoly = RectWaveCoeff._datatype_load( list_json_files[ifile].filename) list_coef_rect_wpoly.append(coef_rect_wpoly) # --- # Initialize structure to save results into an ouptut JSON file outdict = {} outdict['refined_boundary_model'] = refined_boundary_model.__getstate__() outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'rectification and wavelength calibration polynomial coefficients ' \ 'as a function of csu_bar_slit_center for MOS' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['wpoly_longslits'] = {} for ifile in range(nfiles): cdum = 'longslit_' + str(ifile + 1).zfill(3) + '_uuid' outdict['meta_info']['origin']['wpoly_longslits'][cdum] = \ list_coef_rect_wpoly[ifile].uuid outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} # include bb_nc1_orig, bb_nc2_orig and ymargin_bb for each slitlet # (note that the values of bb_ns1_orig and bb_ns2_orig cannot be # computed at this stage because they depend on csu_bar_slit_center) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet] = dict_bb_param[cslitlet] # check that order for rectification transformations is the same for all # the slitlets and longslit configurations order_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: ttd_order = tmpdict[islitlet - 1]['ttd_order'] if ttd_order is not None: order_check_list.append(ttd_order) ttd_order_modeled = \ tmpdict[islitlet - 1]['ttd_order_longslit_model'] order_check_list.append(ttd_order_modeled) # remove duplicates in list order_no_duplicates = list(set(order_check_list)) if len(order_no_duplicates) != 1: print('order_no_duplicates:', order_no_duplicates) raise ValueError('tdd_order is not constant!') ttd_order = int(order_no_duplicates[0]) ncoef_rect = ncoef_fmap(ttd_order) if abs(args.debugplot) >= 10: print('>>> ttd_order........:', ttd_order) print('>>> ncoef_rect.......:', ncoef_rect) # check that polynomial degree in frontiers and spectrails are the same poldeg_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: tmppoly = tmpdict[islitlet - 1]['frontier']['poly_coef_lower'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['frontier']['poly_coef_upper'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_lower'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_middle'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['spectrail']['poly_coef_upper'] poldeg_check_list.append(len(tmppoly) - 1) # remove duplicates in list poldeg_no_duplicates = list(set(poldeg_check_list)) if len(poldeg_no_duplicates) != 1: print('poldeg_no_duplicates:', poldeg_no_duplicates) raise ValueError('poldeg is not constant in frontiers and ' 'spectrails!') poldeg_spectrails = int(poldeg_no_duplicates[0]) if abs(args.debugplot) >= 10: print('>>> poldeg spectrails:', poldeg_spectrails) # check that polynomial degree of wavelength calibration is the same for # all the slitlets poldeg_check_list = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents for islitlet in list_valid_islitlets: tmppoly = tmpdict[islitlet - 1]['wpoly_coeff'] poldeg_check_list.append(len(tmppoly) - 1) tmppoly = tmpdict[islitlet - 1]['wpoly_coeff_longslit_model'] poldeg_check_list.append(len(tmppoly) - 1) # remove duplicates in list poldeg_no_duplicates = list(set(poldeg_check_list)) if len(poldeg_no_duplicates) != 1: print('poldeg_no_duplicates:', poldeg_no_duplicates) raise ValueError('poldeg is not constant in wavelength calibration ' 'polynomials!') poldeg_wavecal = int(poldeg_no_duplicates[0]) if abs(args.debugplot) >= 10: print('>>> poldeg wavecal...:', poldeg_wavecal) # --- # csu_bar_slit_center values for each slitlet print("CSU_bar_slit_center values:") for islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS) cslitlet = 'slitlet' + str(islitlet).zfill(2) list_csu_bar_slit_center = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents[islitlet - 1] csu_bar_slit_center = tmpdict['csu_bar_slit_center'] list_csu_bar_slit_center.append(csu_bar_slit_center) # check that list_csu_bar_slit_center is properly sorted if not np.all( list_csu_bar_slit_center[:-1] <= list_csu_bar_slit_center[1:]): print('cslitlet: ', cslitlet) print('list_csu_bar_slit_center: ', list_csu_bar_slit_center) raise ValueError('Unsorted list_csu_bar_slit_center') outdict['contents'][cslitlet]['list_csu_bar_slit_center'] = \ list_csu_bar_slit_center print('OK!') # --- # rectification polynomial coefficients # note: when aij and bij have not been computed, we use the modeled # version aij_longslit_model and bij_longslit_model print("Rectification polynomial coefficients:") for islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS) cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet]['ttd_order'] = ttd_order outdict['contents'][cslitlet]['ncoef_rect'] = ncoef_rect for keycoef in ['ttd_aij', 'ttd_bij', 'tti_aij', 'tti_bij']: for icoef in range(ncoef_rect): ccoef = str(icoef).zfill(2) list_cij = [] for ifile in range(nfiles): tmpdict = \ list_coef_rect_wpoly[ifile].contents[islitlet - 1] cij = tmpdict[keycoef] if cij is not None: list_cij.append(cij[icoef]) else: cij_modeled = tmpdict[keycoef + '_longslit_model'] if cij_modeled is None: raise ValueError("Unexpected cij_modeled=None!") else: list_cij.append(cij_modeled[icoef]) if abs(args.debugplot) >= 10: print("Warning: using " + keycoef + "_longslit_model for " + cslitlet + " in file " + list_json_files[ifile].filename) cdum = 'list_' + keycoef + '_' + ccoef outdict['contents'][cslitlet][cdum] = list_cij print('OK!') # --- # wavelength calibration polynomial coefficients # note: when wpoly_coeff have not been computed, we use the # wpoly_coeff_longslit_model print("Wavelength calibration polynomial coefficients:") for islitlet in list_valid_islitlets: islitlet_progress(islitlet, EMIR_NBARS) cslitlet = 'slitlet' + str(islitlet).zfill(2) outdict['contents'][cslitlet]['wpoly_degree'] = poldeg_wavecal for icoef in range(poldeg_wavecal + 1): ccoef = str(icoef).zfill(2) list_cij = [] for ifile in range(nfiles): tmpdict = list_coef_rect_wpoly[ifile].contents[islitlet - 1] cij = tmpdict['wpoly_coeff'] if cij is not None: list_cij.append(cij[icoef]) else: cij_modeled = tmpdict['wpoly_coeff_longslit_model'] if cij_modeled is None: raise ValueError("Unexpected cij_modeled=None!") else: list_cij.append(cij_modeled[icoef]) if abs(args.debugplot) >= 10: print("Warning: using wpoly_coeff_longslit_model" + " for " + cslitlet + " in file " + list_json_files[ifile].filename) outdict['contents'][cslitlet]['list_wpoly_coeff_' + ccoef] = \ list_cij print('OK!') # --- # OBSOLETE # Save resulting JSON structure ''' with open(args.out_MOSlibrary.name + '_old', 'w') as fstream: json.dump(outdict, fstream, indent=2, sort_keys=True) print('>>> Saving file ' + args.out_MOSlibrary.name + '_old') ''' # -- # Create object of type MasterRectWave with library of coefficients # for rectification and wavelength calibration master_rectwv = MasterRectWave(instrument='EMIR') master_rectwv.quality_control = numina.types.qc.QC.GOOD master_rectwv.tags['grism'] = grism_name master_rectwv.tags['filter'] = filter_name master_rectwv.meta_info['dtu_configuration'] = outdict['dtu_configuration'] master_rectwv.meta_info['refined_boundary_model'] = { 'parmodel': refined_boundary_model.meta_info['parmodel'] } master_rectwv.meta_info['refined_boundary_model'].update( outdict['refined_boundary_model']['contents']) master_rectwv.total_slitlets = EMIR_NBARS master_rectwv.meta_info['origin'] = { 'bound_param': 'uuid' + refined_boundary_model.uuid, 'longslit_frames': [ 'uuid:' + list_coef_rect_wpoly[ifile].uuid for ifile in range(nfiles) ] } for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: dumdict.update({ 'bb_nc1_orig': 0, 'bb_nc2_orig': 0, 'ymargin_bb': 0, 'list_csu_bar_slit_center': [], 'ttd_order': 0, 'ncoef_rect': 0, 'wpolydegree': 0 }) master_rectwv.missing_slitlets.append(islitlet) master_rectwv.contents.append(dumdict) master_rectwv.writeto(args.out_MOSlibrary.name) print('>>> Saving file ' + args.out_MOSlibrary.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: apply rectification and wavelength ' 'calibration polynomials for the CSU configuration of a ' 'particular image') # required arguments parser.add_argument("fitsfile", help="Input FITS file", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--outfile", required=True, help="Output FITS file with rectified and " "wavelength calibrated image", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_dtu_configuration", help="Ignore DTU configurations differences between " "transformation and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- logging_from_debugplot(args.debugplot) # generate RectWaveCoeff object rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # generate HDUList object # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) # rectification and wavelength calibration reduced_arc = apply_rectwv_coeff( hdulist, rectwv_coeff, args_resampling=args.resampling, args_ignore_dtu_configuration=args.ignore_dtu_configuration, debugplot=args.debugplot) # save result reduced_arc.writeto(args.outfile, overwrite=True)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: apply rectification and wavelength ' 'calibration polynomials for the CSU configuration of a ' 'particular image' ) # required arguments parser.add_argument("fitsfile", help="Input FITS file", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--outfile", required=True, help="Output FITS file with rectified and " "wavelength calibrated image", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_dtu_configuration", help="Ignore DTU configurations differences between " "transformation and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- logging_from_debugplot(args.debugplot) # generate RectWaveCoeff object rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # generate HDUList object # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) # rectification and wavelength calibration reduced_arc = apply_rectwv_coeff( hdulist, rectwv_coeff, args_resampling=args.resampling, args_ignore_dtu_configuration=args.ignore_dtu_configuration, debugplot=args.debugplot ) # save result reduced_arc.writeto(args.outfile, overwrite=True)
def rectwv_coeff_from_mos_library(reduced_image, master_rectwv, ignore_dtu_configuration=True, debugplot=0): """Evaluate rect.+wavecal. coefficients from MOS library Parameters ---------- reduced_image : HDUList object Image with preliminary basic reduction: bpm, bias, dark and flatfield. master_rectwv : MasterRectWave instance Rectification and Wavelength Calibrartion Library product. Contains the library of polynomial coefficients necessary to generate an instance of RectWaveCoeff with the rectification and wavelength calibration coefficients for the particular CSU configuration. ignore_dtu_configuration : bool If True, ignore differences in DTU configuration. debugplot : int Debugging level for messages and plots. For details see 'numina.array.display.pause_debugplot.py'. Returns ------- rectwv_coeff : RectWaveCoeff instance Rectification and wavelength calibration coefficients for the particular CSU configuration. """ logger = logging.getLogger(__name__) logger.info('Computing expected RectWaveCoeff from CSU configuration') # header header = reduced_image[0].header # read the CSU configuration from the image header csu_conf = CsuConfiguration.define_from_header(header) # read the DTU configuration from the image header dtu_conf = DtuConfiguration.define_from_header(header) # retrieve DTU configuration from MasterRectWave object dtu_conf_calib = DtuConfiguration.define_from_dictionary( master_rectwv.meta_info['dtu_configuration']) # check that the DTU configuration employed to obtain the calibration # corresponds to the DTU configuration in the input FITS file if dtu_conf != dtu_conf_calib: if ignore_dtu_configuration: logger.warning('DTU configuration differences found!') else: logger.info('DTU configuration from image header:') logger.info(dtu_conf) logger.info('DTU configuration from master calibration:') logger.info(dtu_conf_calib) raise ValueError("DTU configurations do not match!") else: logger.info('DTU configuration match!') # check grism and filter filter_name = header['filter'] logger.debug('Filter: ' + filter_name) if filter_name != master_rectwv.tags['filter']: raise ValueError('Filter name does not match!') grism_name = header['grism'] logger.debug('Grism: ' + grism_name) if grism_name != master_rectwv.tags['grism']: raise ValueError('Grism name does not match!') # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in master_rectwv.missing_slitlets: list_valid_islitlets.remove(idel) logger.debug('valid slitlet numbers: ' + str(list_valid_islitlets)) # initialize intermediate dictionary with relevant information # (note: this dictionary corresponds to an old structure employed to # store the information in a JSON file; this is no longer necessary, # but here we reuse that dictionary for convenience) outdict = {} outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'computation of rectification and wavelength calibration polynomial ' \ 'coefficients for a particular CSU configuration from a MOS model ' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['fits_frame_uuid'] = 'TBD' outdict['meta_info']['origin']['rect_wpoly_mos_uuid'] = \ master_rectwv.uuid outdict['meta_info']['origin']['fitted_boundary_param_uuid'] = \ master_rectwv.meta_info['origin']['bound_param'] outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} # compute rectification and wavelength calibration coefficients for each # slitlet according to its csu_bar_slit_center value for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # csu_bar_slit_center of current slitlet in initial FITS image csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) # input data structure tmpdict = master_rectwv.contents[islitlet - 1] list_csu_bar_slit_center = tmpdict['list_csu_bar_slit_center'] # check extrapolations if csu_bar_slit_center < min(list_csu_bar_slit_center): logger.warning('extrapolating table with ' + cslitlet) logger.warning('minimum tabulated value: ' + str(min(list_csu_bar_slit_center))) logger.warning('sought value...........: ' + str(csu_bar_slit_center)) if csu_bar_slit_center > max(list_csu_bar_slit_center): logger.warning('extrapolating table with ' + cslitlet) logger.warning('maximum tabulated value: ' + str(max(list_csu_bar_slit_center))) logger.warning('sought value...........: ' + str(csu_bar_slit_center)) # rectification coefficients ttd_order = tmpdict['ttd_order'] ncoef = ncoef_fmap(ttd_order) outdict['contents'][cslitlet] = {} outdict['contents'][cslitlet]['ttd_order'] = ttd_order outdict['contents'][cslitlet]['ttd_order_longslit_model'] = None for keycoef in ['ttd_aij', 'ttd_bij', 'tti_aij', 'tti_bij']: coef_out = [] for icoef in range(ncoef): ccoef = str(icoef).zfill(2) list_cij = tmpdict['list_' + keycoef + '_' + ccoef] funinterp_coef = interp1d(list_csu_bar_slit_center, list_cij, kind='linear', fill_value='extrapolate') # note: funinterp_coef expects a numpy array dum = funinterp_coef([csu_bar_slit_center]) coef_out.append(dum[0]) outdict['contents'][cslitlet][keycoef] = coef_out outdict['contents'][cslitlet][keycoef + '_longslit_model'] = None # wavelength calibration coefficients ncoef = tmpdict['wpoly_degree'] + 1 wpoly_coeff = [] for icoef in range(ncoef): ccoef = str(icoef).zfill(2) list_cij = tmpdict['list_wpoly_coeff_' + ccoef] funinterp_coef = interp1d(list_csu_bar_slit_center, list_cij, kind='linear', fill_value='extrapolate') # note: funinterp_coef expects a numpy array dum = funinterp_coef([csu_bar_slit_center]) wpoly_coeff.append(dum[0]) outdict['contents'][cslitlet]['wpoly_coeff'] = wpoly_coeff outdict['contents'][cslitlet]['wpoly_coeff_longslit_model'] = None # update cdelt1_linear and crval1_linear wpoly_function = np.polynomial.Polynomial(wpoly_coeff) crmin1_linear = wpoly_function(1) crmax1_linear = wpoly_function(EMIR_NAXIS1) cdelt1_linear = (crmax1_linear - crmin1_linear) / (EMIR_NAXIS1 - 1) crval1_linear = crmin1_linear outdict['contents'][cslitlet]['crval1_linear'] = crval1_linear outdict['contents'][cslitlet]['cdelt1_linear'] = cdelt1_linear # update CSU keywords outdict['contents'][cslitlet]['csu_bar_left'] = \ csu_conf.csu_bar_left(islitlet) outdict['contents'][cslitlet]['csu_bar_right'] = \ csu_conf.csu_bar_right(islitlet) outdict['contents'][cslitlet]['csu_bar_slit_center'] = \ csu_conf.csu_bar_slit_center(islitlet) outdict['contents'][cslitlet]['csu_bar_slit_width'] = \ csu_conf.csu_bar_slit_width(islitlet) # for each slitlet compute spectrum trails and frontiers using the # fitted boundary parameters fitted_bound_param_json = { 'contents': master_rectwv.meta_info['refined_boundary_model'] } parmodel = fitted_bound_param_json['contents']['parmodel'] fitted_bound_param_json.update({'meta_info': {'parmodel': parmodel}}) params = bound_params_from_dict(fitted_bound_param_json) if abs(debugplot) >= 10: logger.debug('Fitted boundary parameters:') logger.debug(params.pretty_print()) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # csu_bar_slit_center of current slitlet in initial FITS image csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) # compute and store x0_reference value x0_reference = float(EMIR_NAXIS1) / 2.0 + 0.5 outdict['contents'][cslitlet]['x0_reference'] = x0_reference # compute spectrum trails (lower, middle and upper) list_spectrails = expected_distorted_boundaries(islitlet, csu_bar_slit_center, [0, 0.5, 1], params, parmodel, numpts=101, deg=5, debugplot=0) # store spectrails in output JSON file outdict['contents'][cslitlet]['spectrail'] = {} for idum, cdum in zip(range(3), ['lower', 'middle', 'upper']): outdict['contents'][cslitlet]['spectrail']['poly_coef_' + cdum] = \ list_spectrails[idum].poly_funct.coef.tolist() outdict['contents'][cslitlet]['y0_reference_' + cdum] = \ list_spectrails[idum].poly_funct(x0_reference) # compute frontiers (lower, upper) list_frontiers = expected_distorted_frontiers(islitlet, csu_bar_slit_center, params, parmodel, numpts=101, deg=5, debugplot=0) # store frontiers in output JSON outdict['contents'][cslitlet]['frontier'] = {} for idum, cdum in zip(range(2), ['lower', 'upper']): outdict['contents'][cslitlet]['frontier']['poly_coef_' + cdum] = \ list_frontiers[idum].poly_funct.coef.tolist() outdict['contents'][cslitlet]['y0_frontier_' + cdum] = \ list_frontiers[idum].poly_funct(x0_reference) # store bounding box parameters for each slitlet xdum = np.linspace(1, EMIR_NAXIS1, num=EMIR_NAXIS1) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # parameters already available in the input JSON file for par in ['bb_nc1_orig', 'bb_nc2_orig', 'ymargin_bb']: outdict['contents'][cslitlet][par] = \ master_rectwv.contents[islitlet - 1][par] # estimate bb_ns1_orig and bb_ns2_orig using the already computed # frontiers and the value of ymargin_bb, following the same approach # employed in Slitlet2dArc.__init__() poly_lower_frontier = np.polynomial.Polynomial( outdict['contents'][cslitlet]['frontier']['poly_coef_lower']) poly_upper_frontier = np.polynomial.Polynomial( outdict['contents'][cslitlet]['frontier']['poly_coef_upper']) ylower = poly_lower_frontier(xdum) yupper = poly_upper_frontier(xdum) ymargin_bb = master_rectwv.contents[islitlet - 1]['ymargin_bb'] bb_ns1_orig = int(ylower.min() + 0.5) - ymargin_bb if bb_ns1_orig < 1: bb_ns1_orig = 1 bb_ns2_orig = int(yupper.max() + 0.5) + ymargin_bb if bb_ns2_orig > EMIR_NAXIS2: bb_ns2_orig = EMIR_NAXIS2 outdict['contents'][cslitlet]['bb_ns1_orig'] = bb_ns1_orig outdict['contents'][cslitlet]['bb_ns2_orig'] = bb_ns2_orig # additional parameters (see Slitlet2dArc.__init__) for islitlet in list_valid_islitlets: cslitlet = 'slitlet' + str(islitlet).zfill(2) # define expected frontier ordinates at x0_reference for the rectified # image imposing the vertical length of the slitlet to be constant # and equal to EMIR_NPIXPERSLIT_RECTIFIED outdict['contents'][cslitlet]['y0_frontier_lower_expected'] = \ expected_y0_lower_frontier(islitlet) outdict['contents'][cslitlet]['y0_frontier_upper_expected'] = \ expected_y0_upper_frontier(islitlet) # compute linear transformation to place the rectified slitlet at # the center of the current slitlet bounding box tmpdict = outdict['contents'][cslitlet] xdum1 = tmpdict['y0_frontier_lower'] ydum1 = tmpdict['y0_frontier_lower_expected'] xdum2 = tmpdict['y0_frontier_upper'] ydum2 = tmpdict['y0_frontier_upper_expected'] corr_yrect_b = (ydum2 - ydum1) / (xdum2 - xdum1) corr_yrect_a = ydum1 - corr_yrect_b * xdum1 # compute expected location of rectified boundaries y0_reference_lower_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_lower'] y0_reference_middle_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_middle'] y0_reference_upper_expected = \ corr_yrect_a + corr_yrect_b * tmpdict['y0_reference_upper'] # shift transformation to center the rectified slitlet within the # slitlet bounding box ydummid = (ydum1 + ydum2) / 2 ioffset = int(ydummid - (tmpdict['bb_ns1_orig'] + tmpdict['bb_ns2_orig']) / 2.0) corr_yrect_a -= ioffset # minimum and maximum row in the rectified slitlet encompassing # EMIR_NPIXPERSLIT_RECTIFIED pixels # a) scan number (in pixels, from 1 to NAXIS2) xdum1 = corr_yrect_a + \ corr_yrect_b * tmpdict['y0_frontier_lower'] xdum2 = corr_yrect_a + \ corr_yrect_b * tmpdict['y0_frontier_upper'] # b) row number (starting from zero) min_row_rectified = \ int((round(xdum1 * 10) + 5) / 10) - tmpdict['bb_ns1_orig'] max_row_rectified = \ int((round(xdum2 * 10) - 5) / 10) - tmpdict['bb_ns1_orig'] # save previous results in outdict outdict['contents'][cslitlet]['y0_reference_lower_expected'] = \ y0_reference_lower_expected outdict['contents'][cslitlet]['y0_reference_middle_expected'] = \ y0_reference_middle_expected outdict['contents'][cslitlet]['y0_reference_upper_expected'] = \ y0_reference_upper_expected outdict['contents'][cslitlet]['corr_yrect_a'] = corr_yrect_a outdict['contents'][cslitlet]['corr_yrect_b'] = corr_yrect_b outdict['contents'][cslitlet]['min_row_rectified'] = min_row_rectified outdict['contents'][cslitlet]['max_row_rectified'] = max_row_rectified # --- # Create object of type RectWaveCoeff with coefficients for # rectification and wavelength calibration rectwv_coeff = RectWaveCoeff(instrument='EMIR') rectwv_coeff.quality_control = numina.types.qc.QC.GOOD rectwv_coeff.tags['grism'] = grism_name rectwv_coeff.tags['filter'] = filter_name rectwv_coeff.meta_info['origin']['bound_param'] = \ master_rectwv.meta_info['origin']['bound_param'] rectwv_coeff.meta_info['origin']['master_rectwv'] = \ 'uuid' + master_rectwv.uuid rectwv_coeff.meta_info['dtu_configuration'] = outdict['dtu_configuration'] rectwv_coeff.total_slitlets = EMIR_NBARS for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: dumdict.update({ 'csu_bar_left': csu_conf.csu_bar_left(islitlet), 'csu_bar_right': csu_conf.csu_bar_right(islitlet), 'csu_bar_slit_center': csu_conf.csu_bar_slit_center(islitlet), 'csu_bar_slit_width': csu_conf.csu_bar_slit_width(islitlet), 'x0_reference': float(EMIR_NAXIS1) / 2.0 + 0.5, 'y0_frontier_lower_expected': expected_y0_lower_frontier(islitlet), 'y0_frontier_upper_expected': expected_y0_upper_frontier(islitlet) }) rectwv_coeff.missing_slitlets.append(islitlet) rectwv_coeff.contents.append(dumdict) # debugging __getstate__ and __setstate__ # rectwv_coeff.writeto(args.out_rect_wpoly.name) # print('>>> Saving file ' + args.out_rect_wpoly.name) # check_setstate_getstate(rectwv_coeff, args.out_rect_wpoly.name) logger.info('Generating RectWaveCoeff object with uuid=' + rectwv_coeff.uuid) return rectwv_coeff
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: compute pixel-to-pixel flatfield' ) # required arguments parser.add_argument("fitsfile", help="Input FITS file (flat ON-OFF)", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--minimum_slitlet_width_mm", required=True, help="Minimum slitlet width in mm", type=float) parser.add_argument("--maximum_slitlet_width_mm", required=True, help="Maximum slitlet width in mm", type=float) parser.add_argument("--minimum_fraction", required=True, help="Minimum allowed flatfielding value", type=float, default=0.01) parser.add_argument("--minimum_value_in_output", help="Minimum value allowed in output file: pixels " "below this value are set to 1.0 (default=0.01)", type=float, default=0.01) parser.add_argument("--maximum_value_in_output", help="Maximum value allowed in output file: pixels " "above this value are set to 1.0 (default=10.0)", type=float, default=10.0) # parser.add_argument("--nwindow_median", required=True, # help="Window size to smooth median spectrum in the " # "spectral direction", # type=int) parser.add_argument("--outfile", required=True, help="Output FITS file", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_DTUconf", help="Ignore DTU configurations differences between " "model and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # read calibration structure from JSON file rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) header = hdulist[0].header image2d = hdulist[0].data hdulist.close() # apply global offsets image2d = apply_integer_offsets( image2d=image2d, offx=rectwv_coeff.global_integer_offset_x_pix, offy=rectwv_coeff.global_integer_offset_y_pix ) # protections naxis2, naxis1 = image2d.shape if naxis1 != header['naxis1'] or naxis2 != header['naxis2']: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) raise ValueError('Something is wrong with NAXIS1 and/or NAXIS2') if abs(args.debugplot) >= 10: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) # check that the input FITS file grism and filter match filter_name = header['filter'] if filter_name != rectwv_coeff.tags['filter']: raise ValueError("Filter name does not match!") grism_name = header['grism'] if grism_name != rectwv_coeff.tags['grism']: raise ValueError("Filter name does not match!") if abs(args.debugplot) >= 10: print('>>> grism.......:', grism_name) print('>>> filter......:', filter_name) # check that the DTU configurations are compatible dtu_conf_fitsfile = DtuConfiguration.define_from_fits(args.fitsfile) dtu_conf_jsonfile = DtuConfiguration.define_from_dictionary( rectwv_coeff.meta_info['dtu_configuration']) if dtu_conf_fitsfile != dtu_conf_jsonfile: print('DTU configuration (FITS file):\n\t', dtu_conf_fitsfile) print('DTU configuration (JSON file):\n\t', dtu_conf_jsonfile) if args.ignore_DTUconf: print('WARNING: DTU configuration differences found!') else: raise ValueError('DTU configurations do not match') else: if abs(args.debugplot) >= 10: print('>>> DTU Configuration match!') print(dtu_conf_fitsfile) # load CSU configuration csu_conf_fitsfile = CsuConfiguration.define_from_fits(args.fitsfile) if abs(args.debugplot) >= 10: print(csu_conf_fitsfile) # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in rectwv_coeff.missing_slitlets: print('-> Removing slitlet (not defined):', idel) list_valid_islitlets.remove(idel) # filter out slitlets with widths outside valid range list_outside_valid_width = [] for islitlet in list_valid_islitlets: slitwidth = csu_conf_fitsfile.csu_bar_slit_width(islitlet) if (slitwidth < args.minimum_slitlet_width_mm) or \ (slitwidth > args.maximum_slitlet_width_mm): list_outside_valid_width.append(islitlet) print('-> Removing slitlet (invalid width):', islitlet) if len(list_outside_valid_width) > 0: for idel in list_outside_valid_width: list_valid_islitlets.remove(idel) print('>>> valid slitlet numbers:\n', list_valid_islitlets) # --- # initialize rectified image image2d_flatfielded = np.zeros((EMIR_NAXIS2, EMIR_NAXIS1)) # main loop for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) if abs(args.debugplot) >= 10: print(slt) # extract (distorted) slitlet from the initial image slitlet2d = slt.extract_slitlet2d( image_2k2k=image2d, subtitle='original image' ) # rectify slitlet slitlet2d_rect = slt.rectify( slitlet2d=slitlet2d, resampling=args.resampling, subtitle='original rectified' ) naxis2_slitlet2d, naxis1_slitlet2d = slitlet2d_rect.shape if naxis1_slitlet2d != EMIR_NAXIS1: print('naxis1_slitlet2d: ', naxis1_slitlet2d) print('EMIR_NAXIS1.....: ', EMIR_NAXIS1) raise ValueError("Unexpected naxis1_slitlet2d") # get useful slitlet region (use boundaries instead of frontiers; # note that the nscan_minmax_frontiers() works well independently # of using frontiers of boundaries as arguments) nscan_min, nscan_max = nscan_minmax_frontiers( slt.y0_reference_lower, slt.y0_reference_upper, resize=False ) ii1 = nscan_min - slt.bb_ns1_orig ii2 = nscan_max - slt.bb_ns1_orig + 1 # median spectrum sp_collapsed = np.median(slitlet2d_rect[ii1:(ii2 + 1), :], axis=0) # smooth median spectrum along the spectral direction # sp_median = ndimage.median_filter( # sp_collapsed, # args.nwindow_median, # mode='nearest' # ) xaxis1 = np.arange(1, naxis1_slitlet2d + 1) nremove = 5 spl = AdaptiveLSQUnivariateSpline( x=xaxis1[nremove:-nremove], y=sp_collapsed[nremove:-nremove], t=11, adaptive=True ) xknots = spl.get_knots() yknots = spl(xknots) sp_median = spl(xaxis1) # compute rms within each knot interval nknots = len(xknots) rms_array = np.zeros(nknots - 1, dtype=float) for iknot in range(nknots - 1): residuals = [] for xdum, ydum, yydum in \ zip(xaxis1, sp_collapsed, sp_median): if xknots[iknot] <= xdum <= xknots[iknot + 1]: residuals.append(abs(ydum - yydum)) if len(residuals) > 5: rms_array[iknot] = np.std(residuals) else: rms_array[iknot] = 0 # determine in which knot interval falls each pixel iknot_array = np.zeros(len(xaxis1), dtype=int) for idum, xdum in enumerate(xaxis1): for iknot in range(nknots - 1): if xknots[iknot] <= xdum <= xknots[iknot + 1]: iknot_array[idum] = iknot # compute new fit removing deviant points (with fixed knots) xnewfit = [] ynewfit = [] for idum in range(len(xaxis1)): delta_sp = abs(sp_collapsed[idum] - sp_median[idum]) rms_tmp = rms_array[iknot_array[idum]] if idum == 0 or idum == (len(xaxis1) - 1): lok = True elif rms_tmp > 0: if delta_sp < 3.0 * rms_tmp: lok = True else: lok = False else: lok = True if lok: xnewfit.append(xaxis1[idum]) ynewfit.append(sp_collapsed[idum]) nremove = 5 splnew = AdaptiveLSQUnivariateSpline( x=xnewfit[nremove:-nremove], y=ynewfit[nremove:-nremove], t=xknots[1:-1], adaptive=False ) sp_median = splnew(xaxis1) ymax_spmedian = sp_median.max() y_threshold = ymax_spmedian * args.minimum_fraction sp_median[np.where(sp_median < y_threshold)] = 0.0 if abs(args.debugplot) > 10: title = 'Slitlet#' + str(islitlet) + ' (median spectrum)' ax = ximplotxy(xaxis1, sp_collapsed, title=title, show=False, **{'label' : 'collapsed spectrum'}) ax.plot(xaxis1, sp_median, label='fitted spectrum') ax.plot([1, naxis1_slitlet2d], 2*[y_threshold], label='threshold') ax.plot(xknots, yknots, 'o', label='knots') ax.legend() ax.set_ylim(-0.05*ymax_spmedian, 1.05*ymax_spmedian) pause_debugplot(args.debugplot, pltshow=True, tight_layout=True) # generate rectified slitlet region filled with the median spectrum slitlet2d_rect_spmedian = np.tile(sp_median, (naxis2_slitlet2d, 1)) if abs(args.debugplot) > 10: slt.ximshow_rectified( slitlet2d_rect=slitlet2d_rect_spmedian, subtitle='rectified, filled with median spectrum' ) # unrectified image slitlet2d_unrect_spmedian = slt.rectify( slitlet2d=slitlet2d_rect_spmedian, resampling=args.resampling, inverse=True, subtitle='unrectified, filled with median spectrum' ) # normalize initial slitlet image (avoid division by zero) slitlet2d_norm = np.zeros_like(slitlet2d) for j in range(naxis1_slitlet2d): for i in range(naxis2_slitlet2d): den = slitlet2d_unrect_spmedian[i, j] if den == 0: slitlet2d_norm[i, j] = 1.0 else: slitlet2d_norm[i, j] = slitlet2d[i, j] / den if abs(args.debugplot) > 10: slt.ximshow_unrectified( slitlet2d=slitlet2d_norm, subtitle='unrectified, pixel-to-pixel' ) # check for pseudo-longslit with previous slitlet if islitlet > 1: if (islitlet - 1) in list_valid_islitlets: c1 = csu_conf_fitsfile.csu_bar_slit_center(islitlet - 1) w1 = csu_conf_fitsfile.csu_bar_slit_width(islitlet - 1) c2 = csu_conf_fitsfile.csu_bar_slit_center(islitlet) w2 = csu_conf_fitsfile.csu_bar_slit_width(islitlet) if abs(w1-w2)/w1 < 0.25: wmean = (w1 + w2) / 2.0 if abs(c1 - c2) < wmean/4.0: same_slitlet_below = True else: same_slitlet_below = False else: same_slitlet_below = False else: same_slitlet_below = False else: same_slitlet_below = False # check for pseudo-longslit with previous slitlet if islitlet < EMIR_NBARS: if (islitlet + 1) in list_valid_islitlets: c1 = csu_conf_fitsfile.csu_bar_slit_center(islitlet) w1 = csu_conf_fitsfile.csu_bar_slit_width(islitlet) c2 = csu_conf_fitsfile.csu_bar_slit_center(islitlet + 1) w2 = csu_conf_fitsfile.csu_bar_slit_width(islitlet + 1) if abs(w1-w2)/w1 < 0.25: wmean = (w1 + w2) / 2.0 if abs(c1 - c2) < wmean/4.0: same_slitlet_above = True else: same_slitlet_above = False else: same_slitlet_above = False else: same_slitlet_above = False else: same_slitlet_above = False for j in range(EMIR_NAXIS1): xchannel = j + 1 y0_lower = slt.list_frontiers[0](xchannel) y0_upper = slt.list_frontiers[1](xchannel) n1, n2 = nscan_minmax_frontiers(y0_frontier_lower=y0_lower, y0_frontier_upper=y0_upper, resize=True) # note that n1 and n2 are scans (ranging from 1 to NAXIS2) nn1 = n1 - slt.bb_ns1_orig + 1 nn2 = n2 - slt.bb_ns1_orig + 1 image2d_flatfielded[(n1 - 1):n2, j] = \ slitlet2d_norm[(nn1 - 1):nn2, j] # force to 1.0 region around frontiers if not same_slitlet_below: image2d_flatfielded[(n1 - 1):(n1 + 2), j] = 1 if not same_slitlet_above: image2d_flatfielded[(n2 - 5):n2, j] = 1 else: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) if args.debugplot == 0: print('OK!') # restore global offsets image2d_flatfielded = apply_integer_offsets( image2d=image2d_flatfielded , offx=-rectwv_coeff.global_integer_offset_x_pix, offy=-rectwv_coeff.global_integer_offset_y_pix ) # set pixels below minimum value to 1.0 filtered = np.where(image2d_flatfielded < args.minimum_value_in_output) image2d_flatfielded[filtered] = 1.0 # set pixels above maximum value to 1.0 filtered = np.where(image2d_flatfielded > args.maximum_value_in_output) image2d_flatfielded[filtered] = 1.0 # save output file save_ndarray_to_fits( array=image2d_flatfielded, file_name=args.outfile, main_header=header, overwrite=True ) print('>>> Saving file ' + args.outfile.name)
def rectwv_coeff_from_arc_image(reduced_image, bound_param, lines_catalog, args_nbrightlines=None, args_ymargin_bb=2, args_remove_sp_background=True, args_times_sigma_threshold=10, args_order_fmap=2, args_sigma_gaussian_filtering=2, args_margin_npix=50, args_poldeg_initial=3, args_poldeg_refined=5, args_interactive=False, args_threshold_wv=0, args_ylogscale=False, args_pdf=None, args_geometry=(0,0,640,480), debugplot=0): """Evaluate rect.+wavecal. coefficients from arc image Parameters ---------- reduced_image : HDUList object Image with preliminary basic reduction: bpm, bias, dark and flatfield. bound_param : RefinedBoundaryModelParam instance Refined boundary model. lines_catalog : Numpy array 2D numpy array with the contents of the master file with the expected arc line wavelengths. args_nbrightlines : int TBD args_ymargin_bb : int TBD args_remove_sp_background : bool TBD args_times_sigma_threshold : float TBD args_order_fmap : int TBD args_sigma_gaussian_filtering : float TBD args_margin_npix : int TBD args_poldeg_initial : int TBD args_poldeg_refined : int TBD args_interactive : bool TBD args_threshold_wv : float TBD args_ylogscale : bool TBD args_pdf : TBD args_geometry : TBD debugplot : int Debugging level for messages and plots. For details see 'numina.array.display.pause_debugplot.py'. Returns ------- rectwv_coeff : RectWaveCoeff instance Rectification and wavelength calibration coefficients for the particular CSU configuration of the input arc image. reduced_55sp : HDUList object Image with 55 spectra corresponding to the median spectrum for each slitlet, employed to derived the wavelength calibration polynomial. """ logger = logging.getLogger(__name__) # protections if args_interactive and args_pdf is not None: logger.error('--interactive and --pdf are incompatible options') raise ValueError('--interactive and --pdf are incompatible options') # header and data array header = reduced_image[0].header image2d = reduced_image[0].data # check grism and filter filter_name = header['filter'] logger.info('Filter: ' + filter_name) if filter_name != bound_param.tags['filter']: raise ValueError('Filter name does not match!') grism_name = header['grism'] logger.info('Grism: ' + grism_name) if grism_name != bound_param.tags['grism']: raise ValueError('Grism name does not match!') # read the CSU configuration from the image header csu_conf = CsuConfiguration.define_from_header(header) logger.debug(csu_conf) # read the DTU configuration from the image header dtu_conf = DtuConfiguration.define_from_header(header) logger.debug(dtu_conf) # set boundary parameters parmodel = bound_param.meta_info['parmodel'] params = bound_params_from_dict(bound_param.__getstate__()) if abs(debugplot) >= 10: print('-' * 83) print('* FITTED BOUND PARAMETERS') params.pretty_print() pause_debugplot(debugplot) # determine parameters according to grism+filter combination wv_parameters = set_wv_parameters(filter_name, grism_name) islitlet_min = wv_parameters['islitlet_min'] islitlet_max = wv_parameters['islitlet_max'] if args_nbrightlines is None: nbrightlines = wv_parameters['nbrightlines'] else: nbrightlines = [int(idum) for idum in args_nbrightlines.split(',')] poly_crval1_linear = wv_parameters['poly_crval1_linear'] poly_cdelt1_linear = wv_parameters['poly_cdelt1_linear'] wvmin_expected = wv_parameters['wvmin_expected'] wvmax_expected = wv_parameters['wvmax_expected'] wvmin_useful = wv_parameters['wvmin_useful'] wvmax_useful = wv_parameters['wvmax_useful'] # list of slitlets to be computed logger.info('list_slitlets: [' + str(islitlet_min) + ',... ' + str(islitlet_max) + ']') # read master arc line wavelengths (only brightest lines) wv_master = read_wv_master_from_array( master_table=lines_catalog, lines='brightest', debugplot=debugplot ) # read master arc line wavelengths (whole data set) wv_master_all = read_wv_master_from_array( master_table=lines_catalog, lines='all', debugplot=debugplot ) # check that the arc lines in the master file are properly sorted # in ascending order for i in range(len(wv_master_all) - 1): if wv_master_all[i] >= wv_master_all[i + 1]: logger.error('>>> wavelengths: ' + str(wv_master_all[i]) + ' ' + str(wv_master_all[i+1])) raise ValueError('Arc lines are not sorted in master file') # --- image2d_55sp = np.zeros((EMIR_NBARS, EMIR_NAXIS1)) # compute rectification transformation and wavelength calibration # polynomials measured_slitlets = [] cout = '0' for islitlet in range(1, EMIR_NBARS + 1): if islitlet_min <= islitlet <= islitlet_max: # define Slitlet2dArc object slt = Slitlet2dArc( islitlet=islitlet, csu_conf=csu_conf, ymargin_bb=args_ymargin_bb, params=params, parmodel=parmodel, debugplot=debugplot ) # extract 2D image corresponding to the selected slitlet, clipping # the image beyond the unrectified slitlet (in order to isolate # the arc lines of the current slitlet; otherwise there are # problems with arc lines from neighbour slitlets) image2d_tmp = select_unrectified_slitlet( image2d=image2d, islitlet=islitlet, csu_bar_slit_center=csu_conf.csu_bar_slit_center(islitlet), params=params, parmodel=parmodel, maskonly=False ) slitlet2d = slt.extract_slitlet2d(image2d_tmp) # subtract smooth background computed as follows: # - median collapsed spectrum of the whole slitlet2d # - independent median filtering of the previous spectrum in the # two halves in the spectral direction if args_remove_sp_background: spmedian = np.median(slitlet2d, axis=0) naxis1_tmp = spmedian.shape[0] jmidpoint = naxis1_tmp // 2 sp1 = medfilt(spmedian[:jmidpoint], [201]) sp2 = medfilt(spmedian[jmidpoint:], [201]) spbackground = np.concatenate((sp1, sp2)) slitlet2d -= spbackground # locate unknown arc lines slt.locate_unknown_arc_lines( slitlet2d=slitlet2d, times_sigma_threshold=args_times_sigma_threshold) # continue working with current slitlet only if arc lines have # been detected if slt.list_arc_lines is not None: # compute intersections between spectrum trails and arc lines slt.xy_spectrail_arc_intersections(slitlet2d=slitlet2d) # compute rectification transformation slt.estimate_tt_to_rectify(order=args_order_fmap, slitlet2d=slitlet2d) # rectify image slitlet2d_rect = slt.rectify(slitlet2d, resampling=2, transformation=1) # median spectrum and line peaks from rectified image sp_median, fxpeaks = slt.median_spectrum_from_rectified_image( slitlet2d_rect, sigma_gaussian_filtering=args_sigma_gaussian_filtering, nwinwidth_initial=5, nwinwidth_refined=5, times_sigma_threshold=5, npix_avoid_border=6, nbrightlines=nbrightlines ) image2d_55sp[islitlet - 1, :] = sp_median # determine expected wavelength limits prior to the wavelength # calibration csu_bar_slit_center = csu_conf.csu_bar_slit_center(islitlet) crval1_linear = poly_crval1_linear(csu_bar_slit_center) cdelt1_linear = poly_cdelt1_linear(csu_bar_slit_center) expected_wvmin = crval1_linear - \ args_margin_npix * cdelt1_linear naxis1_linear = sp_median.shape[0] crvaln_linear = crval1_linear + \ (naxis1_linear - 1) * cdelt1_linear expected_wvmax = crvaln_linear + \ args_margin_npix * cdelt1_linear # override previous estimates when necessary if wvmin_expected is not None: expected_wvmin = wvmin_expected if wvmax_expected is not None: expected_wvmax = wvmax_expected # clip initial master arc line list with bright lines to # the expected wavelength range lok1 = expected_wvmin <= wv_master lok2 = wv_master <= expected_wvmax lok = lok1 * lok2 wv_master_eff = wv_master[lok] # perform initial wavelength calibration solution_wv = wvcal_spectrum( sp=sp_median, fxpeaks=fxpeaks, poly_degree_wfit=args_poldeg_initial, wv_master=wv_master_eff, wv_ini_search=expected_wvmin, wv_end_search=expected_wvmax, wvmin_useful=wvmin_useful, wvmax_useful=wvmax_useful, geometry=args_geometry, debugplot=slt.debugplot ) # store initial wavelength calibration polynomial in current # slitlet instance slt.wpoly = np.polynomial.Polynomial(solution_wv.coeff) pause_debugplot(debugplot) # clip initial master arc line list with all the lines to # the expected wavelength range lok1 = expected_wvmin <= wv_master_all lok2 = wv_master_all <= expected_wvmax lok = lok1 * lok2 wv_master_all_eff = wv_master_all[lok] # clip master arc line list to useful region if wvmin_useful is not None: lok = wvmin_useful <= wv_master_all_eff wv_master_all_eff = wv_master_all_eff[lok] if wvmax_useful is not None: lok = wv_master_all_eff <= wvmax_useful wv_master_all_eff = wv_master_all_eff[lok] # refine wavelength calibration if args_poldeg_refined > 0: plottitle = '[slitlet#{}, refined]'.format(islitlet) poly_refined, yres_summary = refine_arccalibration( sp=sp_median, poly_initial=slt.wpoly, wv_master=wv_master_all_eff, poldeg=args_poldeg_refined, ntimes_match_wv=1, interactive=args_interactive, threshold=args_threshold_wv, plottitle=plottitle, ylogscale=args_ylogscale, geometry=args_geometry, pdf=args_pdf, debugplot=slt.debugplot ) # store refined wavelength calibration polynomial in # current slitlet instance slt.wpoly = poly_refined # compute approximate linear values for CRVAL1 and CDELT1 naxis1_linear = sp_median.shape[0] crmin1_linear = slt.wpoly(1) crmax1_linear = slt.wpoly(naxis1_linear) slt.crval1_linear = crmin1_linear slt.cdelt1_linear = \ (crmax1_linear - crmin1_linear) / (naxis1_linear - 1) # check that the trimming of wv_master and wv_master_all has # preserved the wavelength range [crmin1_linear, crmax1_linear] if crmin1_linear < expected_wvmin: logger.warning(">>> islitlet: " +str(islitlet)) logger.warning("expected_wvmin: " + str(expected_wvmin)) logger.warning("crmin1_linear.: " + str(crmin1_linear)) logger.warning("WARNING: Unexpected crmin1_linear < " "expected_wvmin") if crmax1_linear > expected_wvmax: logger.warning(">>> islitlet: " +str(islitlet)) logger.warning("expected_wvmax: " + str(expected_wvmax)) logger.warning("crmax1_linear.: " + str(crmax1_linear)) logger.warning("WARNING: Unexpected crmax1_linear > " "expected_wvmax") cout += '.' else: cout += 'x' if islitlet % 10 == 0: if cout != 'x': cout = str(islitlet // 10) if debugplot != 0: pause_debugplot(debugplot) else: # define Slitlet2dArc object slt = Slitlet2dArc( islitlet=islitlet, csu_conf=csu_conf, ymargin_bb=args_ymargin_bb, params=None, parmodel=None, debugplot=debugplot ) cout += 'i' # store current slitlet in list of measured slitlets measured_slitlets.append(slt) logger.info(cout) # --- # generate FITS file structure with 55 spectra corresponding to the # median spectrum for each slitlet reduced_55sp = fits.PrimaryHDU(data=image2d_55sp) reduced_55sp.header['crpix1'] = (0.0, 'reference pixel') reduced_55sp.header['crval1'] = (0.0, 'central value at crpix2') reduced_55sp.header['cdelt1'] = (1.0, 'increment') reduced_55sp.header['ctype1'] = 'PIXEL' reduced_55sp.header['cunit1'] = ('Pixel', 'units along axis2') reduced_55sp.header['crpix2'] = (0.0, 'reference pixel') reduced_55sp.header['crval2'] = (0.0, 'central value at crpix2') reduced_55sp.header['cdelt2'] = (1.0, 'increment') reduced_55sp.header['ctype2'] = 'PIXEL' reduced_55sp.header['cunit2'] = ('Pixel', 'units along axis2') # --- # Generate structure to store intermediate results outdict = {} outdict['instrument'] = 'EMIR' outdict['meta_info'] = {} outdict['meta_info']['creation_date'] = datetime.now().isoformat() outdict['meta_info']['description'] = \ 'computation of rectification and wavelength calibration polynomial ' \ 'coefficients for a particular CSU configuration' outdict['meta_info']['recipe_name'] = 'undefined' outdict['meta_info']['origin'] = {} outdict['meta_info']['origin']['bound_param_uuid'] = \ bound_param.uuid outdict['meta_info']['origin']['arc_image_uuid'] = 'undefined' outdict['tags'] = {} outdict['tags']['grism'] = grism_name outdict['tags']['filter'] = filter_name outdict['tags']['islitlet_min'] = islitlet_min outdict['tags']['islitlet_max'] = islitlet_max outdict['dtu_configuration'] = dtu_conf.outdict() outdict['uuid'] = str(uuid4()) outdict['contents'] = {} missing_slitlets = [] for slt in measured_slitlets: islitlet = slt.islitlet if islitlet_min <= islitlet <= islitlet_max: # avoid error when creating a python list of coefficients from # numpy polynomials when the polynomials do not exist (note that # the JSON format doesn't handle numpy arrays and such arrays must # be transformed into native python lists) if slt.wpoly is None: wpoly_coeff = None else: wpoly_coeff = slt.wpoly.coef.tolist() if slt.wpoly_longslit_model is None: wpoly_coeff_longslit_model = None else: wpoly_coeff_longslit_model = \ slt.wpoly_longslit_model.coef.tolist() # avoid similar error when creating a python list of coefficients # when the numpy array does not exist; note that this problem # does not happen with tt?_aij_longslit_model and # tt?_bij_longslit_model because the latter have already been # created as native python lists if slt.ttd_aij is None: ttd_aij = None else: ttd_aij = slt.ttd_aij.tolist() if slt.ttd_bij is None: ttd_bij = None else: ttd_bij = slt.ttd_bij.tolist() if slt.tti_aij is None: tti_aij = None else: tti_aij = slt.tti_aij.tolist() if slt.tti_bij is None: tti_bij = None else: tti_bij = slt.tti_bij.tolist() # creating temporary dictionary with the information corresponding # to the current slitlett that will be saved in the JSON file tmp_dict = { 'csu_bar_left': slt.csu_bar_left, 'csu_bar_right': slt.csu_bar_right, 'csu_bar_slit_center': slt.csu_bar_slit_center, 'csu_bar_slit_width': slt.csu_bar_slit_width, 'x0_reference': slt.x0_reference, 'y0_reference_lower': slt.y0_reference_lower, 'y0_reference_middle': slt.y0_reference_middle, 'y0_reference_upper': slt.y0_reference_upper, 'y0_reference_lower_expected': slt.y0_reference_lower_expected, 'y0_reference_middle_expected': slt.y0_reference_middle_expected, 'y0_reference_upper_expected': slt.y0_reference_upper_expected, 'y0_frontier_lower': slt.y0_frontier_lower, 'y0_frontier_upper': slt.y0_frontier_upper, 'y0_frontier_lower_expected': slt.y0_frontier_lower_expected, 'y0_frontier_upper_expected': slt.y0_frontier_upper_expected, 'corr_yrect_a': slt.corr_yrect_a, 'corr_yrect_b': slt.corr_yrect_b, 'min_row_rectified': slt.min_row_rectified, 'max_row_rectified': slt.max_row_rectified, 'ymargin_bb': slt.ymargin_bb, 'bb_nc1_orig': slt.bb_nc1_orig, 'bb_nc2_orig': slt.bb_nc2_orig, 'bb_ns1_orig': slt.bb_ns1_orig, 'bb_ns2_orig': slt.bb_ns2_orig, 'spectrail': { 'poly_coef_lower': slt.list_spectrails[ slt.i_lower_spectrail].poly_funct.coef.tolist(), 'poly_coef_middle': slt.list_spectrails[ slt.i_middle_spectrail].poly_funct.coef.tolist(), 'poly_coef_upper': slt.list_spectrails[ slt.i_upper_spectrail].poly_funct.coef.tolist(), }, 'frontier': { 'poly_coef_lower': slt.list_frontiers[0].poly_funct.coef.tolist(), 'poly_coef_upper': slt.list_frontiers[1].poly_funct.coef.tolist(), }, 'ttd_order': slt.ttd_order, 'ttd_aij': ttd_aij, 'ttd_bij': ttd_bij, 'tti_aij': tti_aij, 'tti_bij': tti_bij, 'ttd_order_longslit_model': slt.ttd_order_longslit_model, 'ttd_aij_longslit_model': slt.ttd_aij_longslit_model, 'ttd_bij_longslit_model': slt.ttd_bij_longslit_model, 'tti_aij_longslit_model': slt.tti_aij_longslit_model, 'tti_bij_longslit_model': slt.tti_bij_longslit_model, 'wpoly_coeff': wpoly_coeff, 'wpoly_coeff_longslit_model': wpoly_coeff_longslit_model, 'crval1_linear': slt.crval1_linear, 'cdelt1_linear': slt.cdelt1_linear } else: missing_slitlets.append(islitlet) tmp_dict = { 'csu_bar_left': slt.csu_bar_left, 'csu_bar_right': slt.csu_bar_right, 'csu_bar_slit_center': slt.csu_bar_slit_center, 'csu_bar_slit_width': slt.csu_bar_slit_width, 'x0_reference': slt.x0_reference, 'y0_frontier_lower_expected': slt.y0_frontier_lower_expected, 'y0_frontier_upper_expected': slt.y0_frontier_upper_expected } slitlet_label = "slitlet" + str(islitlet).zfill(2) outdict['contents'][slitlet_label] = tmp_dict # --- # OBSOLETE ''' # save JSON file needed to compute the MOS model with open(args.out_json.name, 'w') as fstream: json.dump(outdict, fstream, indent=2, sort_keys=True) print('>>> Saving file ' + args.out_json.name) ''' # --- # Create object of type RectWaveCoeff with coefficients for # rectification and wavelength calibration rectwv_coeff = RectWaveCoeff(instrument='EMIR') rectwv_coeff.quality_control = numina.types.qc.QC.GOOD rectwv_coeff.tags['grism'] = grism_name rectwv_coeff.tags['filter'] = filter_name rectwv_coeff.meta_info['origin']['bound_param'] = \ 'uuid' + bound_param.uuid rectwv_coeff.meta_info['dtu_configuration'] = outdict['dtu_configuration'] rectwv_coeff.total_slitlets = EMIR_NBARS rectwv_coeff.missing_slitlets = missing_slitlets for i in range(EMIR_NBARS): islitlet = i + 1 dumdict = {'islitlet': islitlet} cslitlet = 'slitlet' + str(islitlet).zfill(2) if cslitlet in outdict['contents']: dumdict.update(outdict['contents'][cslitlet]) else: raise ValueError("Unexpected error") rectwv_coeff.contents.append(dumdict) # debugging __getstate__ and __setstate__ # rectwv_coeff.writeto(args.out_json.name) # print('>>> Saving file ' + args.out_json.name) # check_setstate_getstate(rectwv_coeff, args.out_json.name) logger.info('Generating RectWaveCoeff object with uuid=' + rectwv_coeff.uuid) return rectwv_coeff, reduced_55sp
def main(args=None): # parse command-line options parser = argparse.ArgumentParser() # required arguments parser.add_argument("--input_rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration polynomials " "corresponding to a longslit observation", type=argparse.FileType('rt')) parser.add_argument("--output_rectwv_coeff", required=True, help="Output JSON file with updated longslit_model " "coefficients", type=lambda x: arg_file_is_new(parser, x, mode='wt')) # optional arguments parser.add_argument("--geometry", help="tuple x,y,dx,dy (default 0,0,640,480)", default="0,0,640,480") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # --- logging_from_debugplot(args.debugplot) logger = logging.getLogger(__name__) # geometry if args.geometry is None: geometry = None else: tmp_str = args.geometry.split(",") x_geom = int(tmp_str[0]) y_geom = int(tmp_str[1]) dx_geom = int(tmp_str[2]) dy_geom = int(tmp_str[3]) geometry = x_geom, y_geom, dx_geom, dy_geom # generate RectWaveCoeff object rectwv_coeff = RectWaveCoeff._datatype_load( args.input_rectwv_coeff.name) # update longslit_model parameters rectwv_coeff_updated = rectwv_coeff_add_longslit_model( rectwv_coeff=rectwv_coeff, geometry=geometry, debugplot=args.debugplot ) # save updated RectWaveCoeff object into JSON file rectwv_coeff_updated.writeto(args.output_rectwv_coeff.name) logger.info('>>> Saving file ' + args.output_rectwv_coeff.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: compute pixel-to-pixel flatfield' ) # required arguments parser.add_argument("fitsfile", help="Input FITS file (flat ON-OFF)", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--minimum_slitlet_width_mm", required=True, help="Minimum slitlet width in mm", type=float) parser.add_argument("--maximum_slitlet_width_mm", required=True, help="Maximum slitlet width in mm", type=float) parser.add_argument("--minimum_fraction", required=True, help="Minimum allowed flatfielding value", type=float, default=0.01) parser.add_argument("--minimum_value_in_output", help="Minimum value allowed in output file: pixels " "below this value are set to 1.0 (default=0.01)", type=float, default=0.01) parser.add_argument("--maximum_value_in_output", help="Maximum value allowed in output file: pixels " "above this value are set to 1.0 (default=10.0)", type=float, default=10.0) parser.add_argument("--nwindow_median", help="Window size to smooth median spectrum in the " "spectral direction", type=int) parser.add_argument("--outfile", required=True, help="Output FITS file", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_DTUconf", help="Ignore DTU configurations differences between " "model and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # This code is obsolete raise ValueError('This code is obsolete: use recipe in ' 'emirdrp/recipes/spec/flatpix2pix.py') # read calibration structure from JSON file rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) header = hdulist[0].header image2d = hdulist[0].data hdulist.close() # apply global offsets image2d = apply_integer_offsets( image2d=image2d, offx=rectwv_coeff.global_integer_offset_x_pix, offy=rectwv_coeff.global_integer_offset_y_pix ) # protections naxis2, naxis1 = image2d.shape if naxis1 != header['naxis1'] or naxis2 != header['naxis2']: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) raise ValueError('Something is wrong with NAXIS1 and/or NAXIS2') if abs(args.debugplot) >= 10: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) # check that the input FITS file grism and filter match filter_name = header['filter'] if filter_name != rectwv_coeff.tags['filter']: raise ValueError("Filter name does not match!") grism_name = header['grism'] if grism_name != rectwv_coeff.tags['grism']: raise ValueError("Filter name does not match!") if abs(args.debugplot) >= 10: print('>>> grism.......:', grism_name) print('>>> filter......:', filter_name) # check that the DTU configurations are compatible dtu_conf_fitsfile = DtuConfiguration.define_from_fits(args.fitsfile) dtu_conf_jsonfile = DtuConfiguration.define_from_dictionary( rectwv_coeff.meta_info['dtu_configuration']) if dtu_conf_fitsfile != dtu_conf_jsonfile: print('DTU configuration (FITS file):\n\t', dtu_conf_fitsfile) print('DTU configuration (JSON file):\n\t', dtu_conf_jsonfile) if args.ignore_DTUconf: print('WARNING: DTU configuration differences found!') else: raise ValueError('DTU configurations do not match') else: if abs(args.debugplot) >= 10: print('>>> DTU Configuration match!') print(dtu_conf_fitsfile) # load CSU configuration csu_conf_fitsfile = CsuConfiguration.define_from_fits(args.fitsfile) if abs(args.debugplot) >= 10: print(csu_conf_fitsfile) # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in rectwv_coeff.missing_slitlets: print('-> Removing slitlet (not defined):', idel) list_valid_islitlets.remove(idel) # filter out slitlets with widths outside valid range list_outside_valid_width = [] for islitlet in list_valid_islitlets: slitwidth = csu_conf_fitsfile.csu_bar_slit_width(islitlet) if (slitwidth < args.minimum_slitlet_width_mm) or \ (slitwidth > args.maximum_slitlet_width_mm): list_outside_valid_width.append(islitlet) print('-> Removing slitlet (invalid width):', islitlet) if len(list_outside_valid_width) > 0: for idel in list_outside_valid_width: list_valid_islitlets.remove(idel) print('>>> valid slitlet numbers:\n', list_valid_islitlets) # --- # compute and store median spectrum (and masked region) for each # individual slitlet image2d_sp_median = np.zeros((EMIR_NBARS, EMIR_NAXIS1)) image2d_sp_mask = np.zeros((EMIR_NBARS, EMIR_NAXIS1), dtype=bool) for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) if abs(args.debugplot) >= 10: print(slt) # extract (distorted) slitlet from the initial image slitlet2d = slt.extract_slitlet2d( image_2k2k=image2d, subtitle='original image' ) # rectify slitlet slitlet2d_rect = slt.rectify( slitlet2d=slitlet2d, resampling=args.resampling, subtitle='original rectified' ) naxis2_slitlet2d, naxis1_slitlet2d = slitlet2d_rect.shape if naxis1_slitlet2d != EMIR_NAXIS1: print('naxis1_slitlet2d: ', naxis1_slitlet2d) print('EMIR_NAXIS1.....: ', EMIR_NAXIS1) raise ValueError("Unexpected naxis1_slitlet2d") sp_mask = np.zeros(naxis1_slitlet2d, dtype=bool) # for grism LR set to zero data beyond useful wavelength range if grism_name == 'LR': wv_parameters = set_wv_parameters(filter_name, grism_name) x_pix = np.arange(1, naxis1_slitlet2d + 1) wl_pix = polyval(x_pix, slt.wpoly) lremove = wl_pix < wv_parameters['wvmin_useful'] sp_mask[lremove] = True slitlet2d_rect[:, lremove] = 0.0 lremove = wl_pix > wv_parameters['wvmax_useful'] slitlet2d_rect[:, lremove] = 0.0 sp_mask[lremove] = True # get useful slitlet region (use boundaries instead of frontiers; # note that the nscan_minmax_frontiers() works well independently # of using frontiers of boundaries as arguments) nscan_min, nscan_max = nscan_minmax_frontiers( slt.y0_reference_lower, slt.y0_reference_upper, resize=False ) ii1 = nscan_min - slt.bb_ns1_orig ii2 = nscan_max - slt.bb_ns1_orig + 1 # median spectrum sp_collapsed = np.median(slitlet2d_rect[ii1:(ii2 + 1), :], axis=0) # smooth median spectrum along the spectral direction sp_median = ndimage.median_filter( sp_collapsed, args.nwindow_median, mode='nearest' ) """ nremove = 5 spl = AdaptiveLSQUnivariateSpline( x=xaxis1[nremove:-nremove], y=sp_collapsed[nremove:-nremove], t=11, adaptive=True ) xknots = spl.get_knots() yknots = spl(xknots) sp_median = spl(xaxis1) # compute rms within each knot interval nknots = len(xknots) rms_array = np.zeros(nknots - 1, dtype=float) for iknot in range(nknots - 1): residuals = [] for xdum, ydum, yydum in \ zip(xaxis1, sp_collapsed, sp_median): if xknots[iknot] <= xdum <= xknots[iknot + 1]: residuals.append(abs(ydum - yydum)) if len(residuals) > 5: rms_array[iknot] = np.std(residuals) else: rms_array[iknot] = 0 # determine in which knot interval falls each pixel iknot_array = np.zeros(len(xaxis1), dtype=int) for idum, xdum in enumerate(xaxis1): for iknot in range(nknots - 1): if xknots[iknot] <= xdum <= xknots[iknot + 1]: iknot_array[idum] = iknot # compute new fit removing deviant points (with fixed knots) xnewfit = [] ynewfit = [] for idum in range(len(xaxis1)): delta_sp = abs(sp_collapsed[idum] - sp_median[idum]) rms_tmp = rms_array[iknot_array[idum]] if idum == 0 or idum == (len(xaxis1) - 1): lok = True elif rms_tmp > 0: if delta_sp < 3.0 * rms_tmp: lok = True else: lok = False else: lok = True if lok: xnewfit.append(xaxis1[idum]) ynewfit.append(sp_collapsed[idum]) nremove = 5 splnew = AdaptiveLSQUnivariateSpline( x=xnewfit[nremove:-nremove], y=ynewfit[nremove:-nremove], t=xknots[1:-1], adaptive=False ) sp_median = splnew(xaxis1) """ ymax_spmedian = sp_median.max() y_threshold = ymax_spmedian * args.minimum_fraction lremove = np.where(sp_median < y_threshold) sp_median[lremove] = 0.0 sp_mask[lremove] = True image2d_sp_median[islitlet - 1, :] = sp_median image2d_sp_mask[islitlet - 1, :] = sp_mask if abs(args.debugplot) % 10 != 0: xaxis1 = np.arange(1, naxis1_slitlet2d + 1) title = 'Slitlet#' + str(islitlet) + ' (median spectrum)' ax = ximplotxy(xaxis1, sp_collapsed, title=title, show=False, **{'label' : 'collapsed spectrum'}) ax.plot(xaxis1, sp_median, label='fitted spectrum') ax.plot([1, naxis1_slitlet2d], 2*[y_threshold], label='threshold') # ax.plot(xknots, yknots, 'o', label='knots') ax.legend() ax.set_ylim(-0.05*ymax_spmedian, 1.05*ymax_spmedian) pause_debugplot(args.debugplot, pltshow=True, tight_layout=True) else: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) # ToDo: compute "average" spectrum for each pseudo-longslit, scaling # with the median signal in each slitlet; derive a particular # spectrum for each slitlet (scaling properly) image2d_sp_median_masked = np.ma.masked_array( image2d_sp_median, mask=image2d_sp_mask ) ycut_median = np.ma.median(image2d_sp_median_masked, axis=1).data ycut_median_2d = np.repeat(ycut_median, EMIR_NAXIS1).reshape( EMIR_NBARS, EMIR_NAXIS1) image2d_sp_median_eq = image2d_sp_median_masked / ycut_median_2d image2d_sp_median_eq = image2d_sp_median_eq.data if True: ximshow(image2d_sp_median, title='sp_median', debugplot=12) ximplotxy(np.arange(1, EMIR_NBARS + 1), ycut_median, 'ro', title='median value of each spectrum', debugplot=12) ximshow(image2d_sp_median_eq, title='sp_median_eq', debugplot=12) csu_conf_fitsfile.display_pseudo_longslits( list_valid_slitlets=list_valid_islitlets) dict_longslits = csu_conf_fitsfile.pseudo_longslits() # compute median spectrum for each longslit and insert (properly # scaled) that spectrum in each slitlet belonging to that longslit image2d_sp_median_longslit = np.zeros((EMIR_NBARS, EMIR_NAXIS1)) islitlet = 1 loop = True while loop: if islitlet in list_valid_islitlets: imin = dict_longslits[islitlet].imin() imax = dict_longslits[islitlet].imax() print('--> imin, imax: ', imin, imax) sp_median_longslit = np.median( image2d_sp_median_eq[(imin - 1):imax, :], axis=0) for i in range(imin, imax+1): print('----> i: ', i) image2d_sp_median_longslit[(i - 1), :] = \ sp_median_longslit * ycut_median[i - 1] islitlet = imax else: print('--> ignoring: ', islitlet) if islitlet == EMIR_NBARS: loop = False else: islitlet += 1 if True: ximshow(image2d_sp_median_longslit, debugplot=12) # initialize rectified image image2d_flatfielded = np.zeros((EMIR_NAXIS2, EMIR_NAXIS1)) # main loop for islitlet in list(range(1, EMIR_NBARS + 1)): if islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=False) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) # extract (distorted) slitlet from the initial image slitlet2d = slt.extract_slitlet2d( image_2k2k=image2d, subtitle='original image' ) # rectify slitlet slitlet2d_rect = slt.rectify( slitlet2d=slitlet2d, resampling=args.resampling, subtitle='original rectified' ) naxis2_slitlet2d, naxis1_slitlet2d = slitlet2d_rect.shape sp_median = image2d_sp_median_longslit[islitlet - 1, :] # generate rectified slitlet region filled with the median spectrum slitlet2d_rect_spmedian = np.tile(sp_median, (naxis2_slitlet2d, 1)) if abs(args.debugplot) > 10: slt.ximshow_rectified( slitlet2d_rect=slitlet2d_rect_spmedian, subtitle='rectified, filled with median spectrum' ) # unrectified image slitlet2d_unrect_spmedian = slt.rectify( slitlet2d=slitlet2d_rect_spmedian, resampling=args.resampling, inverse=True, subtitle='unrectified, filled with median spectrum' ) # normalize initial slitlet image (avoid division by zero) slitlet2d_norm = np.zeros_like(slitlet2d) for j in range(naxis1_slitlet2d): for i in range(naxis2_slitlet2d): den = slitlet2d_unrect_spmedian[i, j] if den == 0: slitlet2d_norm[i, j] = 1.0 else: slitlet2d_norm[i, j] = slitlet2d[i, j] / den if abs(args.debugplot) > 10: slt.ximshow_unrectified( slitlet2d=slitlet2d_norm, subtitle='unrectified, pixel-to-pixel' ) # check for pseudo-longslit with previous slitlet if islitlet > 1: if (islitlet - 1) in list_valid_islitlets: c1 = csu_conf_fitsfile.csu_bar_slit_center(islitlet - 1) w1 = csu_conf_fitsfile.csu_bar_slit_width(islitlet - 1) c2 = csu_conf_fitsfile.csu_bar_slit_center(islitlet) w2 = csu_conf_fitsfile.csu_bar_slit_width(islitlet) if abs(w1-w2)/w1 < 0.25: wmean = (w1 + w2) / 2.0 if abs(c1 - c2) < wmean/4.0: same_slitlet_below = True else: same_slitlet_below = False else: same_slitlet_below = False else: same_slitlet_below = False else: same_slitlet_below = False # check for pseudo-longslit with next slitlet if islitlet < EMIR_NBARS: if (islitlet + 1) in list_valid_islitlets: c1 = csu_conf_fitsfile.csu_bar_slit_center(islitlet) w1 = csu_conf_fitsfile.csu_bar_slit_width(islitlet) c2 = csu_conf_fitsfile.csu_bar_slit_center(islitlet + 1) w2 = csu_conf_fitsfile.csu_bar_slit_width(islitlet + 1) if abs(w1-w2)/w1 < 0.25: wmean = (w1 + w2) / 2.0 if abs(c1 - c2) < wmean/4.0: same_slitlet_above = True else: same_slitlet_above = False else: same_slitlet_above = False else: same_slitlet_above = False else: same_slitlet_above = False for j in range(EMIR_NAXIS1): xchannel = j + 1 y0_lower = slt.list_frontiers[0](xchannel) y0_upper = slt.list_frontiers[1](xchannel) n1, n2 = nscan_minmax_frontiers(y0_frontier_lower=y0_lower, y0_frontier_upper=y0_upper, resize=True) # note that n1 and n2 are scans (ranging from 1 to NAXIS2) nn1 = n1 - slt.bb_ns1_orig + 1 nn2 = n2 - slt.bb_ns1_orig + 1 image2d_flatfielded[(n1 - 1):n2, j] = \ slitlet2d_norm[(nn1 - 1):nn2, j] # force to 1.0 region around frontiers if not same_slitlet_below: image2d_flatfielded[(n1 - 1):(n1 + 2), j] = 1 if not same_slitlet_above: image2d_flatfielded[(n2 - 5):n2, j] = 1 else: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS, ignore=True) if args.debugplot == 0: print('OK!') # restore global offsets image2d_flatfielded = apply_integer_offsets( image2d=image2d_flatfielded , offx=-rectwv_coeff.global_integer_offset_x_pix, offy=-rectwv_coeff.global_integer_offset_y_pix ) # set pixels below minimum value to 1.0 filtered = np.where(image2d_flatfielded < args.minimum_value_in_output) image2d_flatfielded[filtered] = 1.0 # set pixels above maximum value to 1.0 filtered = np.where(image2d_flatfielded > args.maximum_value_in_output) image2d_flatfielded[filtered] = 1.0 # save output file save_ndarray_to_fits( array=image2d_flatfielded, file_name=args.outfile, main_header=header, overwrite=True ) print('>>> Saving file ' + args.outfile.name)
def main(args=None): # parse command-line options parser = argparse.ArgumentParser( description='description: compute pixel-to-pixel flatfield' ) # required arguments parser.add_argument("fitsfile", help="Input FITS file (flat ON-OFF)", type=argparse.FileType('rb')) parser.add_argument("--rectwv_coeff", required=True, help="Input JSON file with rectification and " "wavelength calibration coefficients", type=argparse.FileType('rt')) parser.add_argument("--minimum_fraction", required=True, help="Minimum allowed flatfielding value", type=float, default=0.01) parser.add_argument("--minimum_value_in_output", help="Minimum value allowed in output file: pixels " "below this value are set to 1.0 (default=0.01)", type=float, default=0.01) parser.add_argument("--nwindow_median", required=True, help="Window size to smooth median spectrum in the " "spectral direction", type=int) parser.add_argument("--outfile", required=True, help="Output FITS file", type=lambda x: arg_file_is_new(parser, x, mode='wb')) # optional arguments parser.add_argument("--delta_global_integer_offset_x_pix", help="Delta global integer offset in the X direction " "(default=0)", default=0, type=int) parser.add_argument("--delta_global_integer_offset_y_pix", help="Delta global integer offset in the Y direction " "(default=0)", default=0, type=int) parser.add_argument("--resampling", help="Resampling method: 1 -> nearest neighbor, " "2 -> linear interpolation (default)", default=2, type=int, choices=(1, 2)) parser.add_argument("--ignore_DTUconf", help="Ignore DTU configurations differences between " "model and input image", action="store_true") parser.add_argument("--debugplot", help="Integer indicating plotting & debugging options" " (default=0)", default=0, type=int, choices=DEBUGPLOT_CODES) parser.add_argument("--echo", help="Display full command line", action="store_true") args = parser.parse_args(args) if args.echo: print('\033[1m\033[31m% ' + ' '.join(sys.argv) + '\033[0m\n') # read calibration structure from JSON file rectwv_coeff = RectWaveCoeff._datatype_load(args.rectwv_coeff.name) # modify (when requested) global offsets rectwv_coeff.global_integer_offset_x_pix += \ args.delta_global_integer_offset_x_pix rectwv_coeff.global_integer_offset_y_pix += \ args.delta_global_integer_offset_y_pix # read FITS image and its corresponding header hdulist = fits.open(args.fitsfile) header = hdulist[0].header image2d = hdulist[0].data hdulist.close() # apply global offsets image2d = apply_integer_offsets( image2d=image2d, offx=rectwv_coeff.global_integer_offset_x_pix, offy=rectwv_coeff.global_integer_offset_y_pix ) # protections naxis2, naxis1 = image2d.shape if naxis1 != header['naxis1'] or naxis2 != header['naxis2']: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) raise ValueError('Something is wrong with NAXIS1 and/or NAXIS2') if abs(args.debugplot) >= 10: print('>>> NAXIS1:', naxis1) print('>>> NAXIS2:', naxis2) # check that the input FITS file grism and filter match filter_name = header['filter'] if filter_name != rectwv_coeff.tags['filter']: raise ValueError("Filter name does not match!") grism_name = header['grism'] if grism_name != rectwv_coeff.tags['grism']: raise ValueError("Filter name does not match!") if abs(args.debugplot) >= 10: print('>>> grism.......:', grism_name) print('>>> filter......:', filter_name) # check that the DTU configurations are compatible dtu_conf_fitsfile = DtuConfiguration.define_from_fits(args.fitsfile) dtu_conf_jsonfile = DtuConfiguration.define_from_dictionary( rectwv_coeff.meta_info['dtu_configuration']) if dtu_conf_fitsfile != dtu_conf_jsonfile: print('DTU configuration (FITS file):\n\t', dtu_conf_fitsfile) print('DTU configuration (JSON file):\n\t', dtu_conf_jsonfile) if args.ignore_DTUconf: print('WARNING: DTU configuration differences found!') else: raise ValueError('DTU configurations do not match') else: if abs(args.debugplot) >= 10: print('>>> DTU Configuration match!') print(dtu_conf_fitsfile) # valid slitlet numbers list_valid_islitlets = list(range(1, EMIR_NBARS + 1)) for idel in rectwv_coeff.missing_slitlets: list_valid_islitlets.remove(idel) if abs(args.debugplot) >= 10: print('>>> valid slitlet numbers:\n', list_valid_islitlets) # --- # initialize rectified image image2d_flatfielded = np.zeros((EMIR_NAXIS2, EMIR_NAXIS1)) # main loop for islitlet in list_valid_islitlets: if args.debugplot == 0: islitlet_progress(islitlet, EMIR_NBARS) # define Slitlet2D object slt = Slitlet2D(islitlet=islitlet, rectwv_coeff=rectwv_coeff, debugplot=args.debugplot) if abs(args.debugplot) >= 10: print(slt) # extract (distorted) slitlet from the initial image slitlet2d = slt.extract_slitlet2d(image2d) # rectify slitlet slitlet2d_rect = slt.rectify( slitlet2d, resampling=args.resampling ) naxis2_slitlet2d, naxis1_slitlet2d = slitlet2d_rect.shape if naxis1_slitlet2d != EMIR_NAXIS1: print('naxis1_slitlet2d: ', naxis1_slitlet2d) print('EMIR_NAXIS1.....: ', EMIR_NAXIS1) raise ValueError("Unexpected naxis1_slitlet2d") # get useful slitlet region (use boundaires instead of frontiers; # note that the nscan_minmax_frontiers() works well independently # of using frontiers of boundaries as arguments) nscan_min, nscan_max = nscan_minmax_frontiers( slt.y0_reference_lower, slt.y0_reference_upper, resize=False ) ii1 = nscan_min - slt.bb_ns1_orig ii2 = nscan_max - slt.bb_ns1_orig + 1 # median spectrum sp_collapsed = np.median(slitlet2d_rect[ii1:(ii2 + 1), :], axis=0) # smooth median spectrum along the spectral direction sp_median = ndimage.median_filter(sp_collapsed, args.nwindow_median, mode='nearest') ymax_spmedian = sp_median.max() y_threshold = ymax_spmedian * args.minimum_fraction sp_median[np.where(sp_median < y_threshold)] = 0.0 if abs(args.debugplot) > 10: title = 'Slitlet#' + str(islitlet) + '(median spectrum)' xdum = np.arange(1, naxis1_slitlet2d + 1) ax = ximplotxy(xdum, sp_collapsed, title=title, show=False, **{'label' : 'collapsed spectrum'}) ax.plot(xdum, sp_median, label='filtered spectrum') ax.plot([1, naxis1_slitlet2d], 2*[y_threshold], label='threshold') ax.legend() ax.set_ylim(-0.05*ymax_spmedian, 1.05*ymax_spmedian) pause_debugplot(args.debugplot, pltshow=True, tight_layout=True) # generate rectified slitlet region filled with the median spectrum slitlet2d_rect_spmedian = np.tile(sp_median, (naxis2_slitlet2d, 1)) if abs(args.debugplot) > 10: slt.ximshow_rectified(slitlet2d_rect_spmedian) # unrectified image slitlet2d_unrect_spmedian = slt.rectify( slitlet2d_rect_spmedian, resampling=args.resampling, inverse=True ) # normalize initial slitlet image (avoid division by zero) slitlet2d_norm = np.zeros_like(slitlet2d) for j in range(naxis1_slitlet2d): for i in range(naxis2_slitlet2d): den = slitlet2d_unrect_spmedian[i, j] if den == 0: slitlet2d_norm[i, j] = 1.0 else: slitlet2d_norm[i, j] = slitlet2d[i, j] / den if abs(args.debugplot) > 10: slt.ximshow_unrectified(slitlet2d_norm) for j in range(EMIR_NAXIS1): xchannel = j + 1 y0_lower = slt.list_frontiers[0](xchannel) y0_upper = slt.list_frontiers[1](xchannel) n1, n2 = nscan_minmax_frontiers(y0_frontier_lower=y0_lower, y0_frontier_upper=y0_upper, resize=True) # note that n1 and n2 are scans (ranging from 1 to NAXIS2) nn1 = n1 - slt.bb_ns1_orig + 1 nn2 = n2 - slt.bb_ns1_orig + 1 image2d_flatfielded[(n1 - 1):n2, j] = \ slitlet2d_norm[(nn1 - 1):nn2, j] # force to 1.0 region around frontiers image2d_flatfielded[(n1 - 1):(n1 + 2), j] = 1 image2d_flatfielded[(n2 - 5):n2, j] = 1 if args.debugplot == 0: print('OK!') # set pixels below minimum value to 1.0 filtered = np.where(image2d_flatfielded < args.minimum_value_in_output) image2d_flatfielded[filtered] = 1.0 # restore global offsets image2d_flatfielded = apply_integer_offsets( image2d=image2d_flatfielded , offx=-rectwv_coeff.global_integer_offset_x_pix, offy=-rectwv_coeff.global_integer_offset_y_pix ) # save output file save_ndarray_to_fits( array=image2d_flatfielded, file_name=args.outfile, main_header=header, overwrite=True ) print('>>> Saving file ' + args.outfile.name)