def display(self, rc): # Instantiate the log log = gemLog.getGeminiLog(logType=rc["logType"], logLevel=rc["logLevel"]) # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "display", "starting")) # Get parameters from RC threshold = rc["threshold"] remove_bias = rc["remove_bias"] # Get inputs adinput = rc.get_inputs_as_astrodata() orig_input = adinput deepcopied = False # Threshold and bias parameters only make sense for SCI extension; # turn it off for others extname = rc["extname"] if extname!="SCI": threshold = None remove_bias = False elif threshold=="None": threshold = None elif threshold=="auto": dqext = np.array([ad["DQ"] for ad in adinput]) mosaic = np.array([((ad.phu_get_key_value( self.timestamp_keys["mosaicDetectors"]) is not None) or (ad.phu_get_key_value( self.timestamp_keys["tileArrays"]) is not None)) for ad in adinput]) if not np.all(dqext): if not np.any(mosaic): # This is the first possible modification to the data; # always deepcopy before proceeding adinput = [deepcopy(ad) for ad in orig_input] deepcopied = True adinput = sdz.add_dq(adinput, bpm=None, copy_input=False, index=rc["index"]) if not isinstance(adinput,list): adinput = [adinput] else: log.warning("Cannot add DQ plane to mosaicked data; " \ "no threshold mask will be displayed") threshold=None # Check whether approximate bias level should be removed if remove_bias: # Copy the original input if necessary, before # modifying it if not deepcopied: adinput = [deepcopy(ad) for ad in orig_input] deepcopied = True new_adinput = [] for ad in adinput: # Check whether data has been bias- or dark-subtracted biasim = ad.phu_get_key_value("BIASIM") darkim = ad.phu_get_key_value("DARKIM") # Check whether data has been overscan-subtracted overscan = np.array([ext.get_key_value("OVERSCAN") for ext in ad["SCI"]]) if np.any(overscan) or biasim or darkim: log.fullinfo("Bias level has already been removed " "from data; no approximate correction " "will be performed") else: # Get the bias level bias_level = gdc.get_bias_level(adinput=ad) if bias_level is not None: # Subtract the bias level from each science extension log.stdinfo("Subtracting approximate bias level " "from %s for display" % ad.filename) log.fullinfo("Bias levels used: %s" % str(bias_level)) ad = ad.sub(bias_level) else: log.warning("Bias level not found for %s; " "approximate bias will not be removed" % ad.filename) new_adinput.append(ad) adinput = new_adinput # Check whether data needs to be tiled before displaying # Otherwise, flatten all desired extensions into a single list tile = rc["tile"] if tile: next = np.array([ad.count_exts(extname) for ad in adinput]) if np.any(next>1): log.fullinfo("Tiling extensions together before displaying") if not deepcopied: adinput = [deepcopy(ad) for ad in orig_input] deepcopied = True adinput = gm.tile_arrays(adinput, tile_all=True, copy_input=False, index=rc["index"]) if not isinstance(adinput,list): adinput = [adinput] else: extinput = [] for ad in adinput: exts = ad[extname] if exts is None: continue for ext in exts: if extname=="SCI" and threshold=="auto": dqext = ad["DQ",ext.extver()] if dqext is not None: ext.append(dqext) extinput.append(ext) adinput = extinput # Get overlays from RC if available (eg. IQ overlays made by measureIQ) # then clear them out so they don't persist to the next display call overlay_dict = gt.make_dict(key_list=adinput, value_list=rc["overlay"]) rc["overlay"] = None # Set the starting frame frame = rc["frame"] if frame is None: frame = 1 # Initialize the local version of numdisplay # (overrides the display function to allow for quick overlays) lnd = _localNumDisplay() # Loop over each input AstroData object in the input list if len(adinput)<1: log.warning("No extensions to display with extname %s" % extname) for ad in adinput: if frame>16: log.warning("Too many images; only the first 16 are displayed.") break # Check for more than one extension ndispext = ad.count_exts(extname) if ndispext==0: log.warning("No extensions to display in "\ "%s with extname %s" % (ad.filename,extname)) continue elif ndispext>1: raise Errors.InputError("Found %i extensions for "\ "%s[%s]; exactly 1 is required" % (ndispext,ad.filename,extname)) dispext = ad[extname] # Squeeze the data to get rid of any empty dimensions # (eg. in raw F2 data) data = np.squeeze(dispext.data) # Check for 1-D data (ie. extracted spectra) if len(data.shape)==1: # Use splot to display instead of numdisplay log.fullinfo("Calling IRAF task splot to display data") splot_task = eti.sploteti.SplotETI(rc,ad) splot_task.run() continue # Make threshold mask if desired masks = [] mask_colors = [] if threshold is not None: if threshold!="auto": # Make threshold mask from user supplied value; # Assume units match units of data threshold = float(threshold) satmask = np.where(data>threshold) else: # Make the mask from the nonlinear and # saturation bits in the DQ plane dqext = ad["DQ",dispext.extver()] if dqext is None: log.warning("No DQ plane found; cannot make threshold "\ "mask") satmask = None else: dqdata = np.squeeze(dqext.data) satmask = np.where(np.logical_or(dqdata & 2, dqdata & 4)) if satmask is not None: masks.append(satmask) mask_colors.append(204) overlay = overlay_dict[ad] if overlay is not None: masks.append(overlay) mask_colors.append(206) # ds9 color codes: should make this into a dictionary # and allow user to specify # # red = 204 # green = 205 # blue = 206 # yellow = 207 # light blue = 208 # magenta = 209 # orange = 210 # dark pink = 211 # bright orange = 212 # light yellow = 213 # pink = 214 # blue-green = 215 # pink = 216 # peach = 217 # Define the display name if tile and extname=="SCI": name = ad.filename elif tile: # numdisplay/ds9 doesn't seem to like square brackets # or spaces in the name, so use parentheses for extension name = "%s(%s)" % (ad.filename,extname) else: name = "%s(%s,%d)" % (ad.filename,extname,dispext.extver()) # Display the data try: lnd.display(data,name=name, frame=frame,zscale=rc["zscale"],quiet=True, masks=masks, mask_colors=mask_colors) except IOError: log.warning("DS9 not found; cannot display input") frame+=1 # Print some statistics for flats if "GMOS_IMAGE_FLAT" in ad.types and extname=="SCI": scidata = ad["SCI"].data dqext = ad["DQ"] if dqext is not None: dqdata = dqext.data good_data = scidata[dqdata==0] else: good_data = scidata log.stdinfo("Twilight flat counts for %s:" % ad.filename) log.stdinfo(" Mean value: %.0f" % np.mean(good_data, dtype=np.float64)) log.stdinfo(" Median value: %.0f" % np.median(good_data)) rc.report_output(orig_input) yield rc
def attachWavelengthSolution(self,rc): # Instantiate the log log = logutils.get_logger(__name__) # Define the keyword to be used for the time stamp timestamp_key = self.timestamp_keys["attachWavelengthSolution"] # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "attachWavelengthSolution", "starting")) # Initialize the list of output AstroData objects adoutput_list = [] # Check for a user-supplied arc adinput = rc.get_inputs_as_astrodata() arc_param = rc["arc"] arc_dict = None if arc_param is not None: # The user supplied an input to the arc parameter if not isinstance(arc_param, list): arc_list = [arc_param] else: arc_list = arc_param # Convert filenames to AD instances if necessary tmp_list = [] for arc in arc_list: if type(arc) is not AstroData: arc = AstroData(arc) tmp_list.append(arc) arc_list = tmp_list arc_dict = gt.make_dict(key_list=adinput, value_list=arc_list) for ad in adinput: if arc_dict is not None: arc = arc_dict[ad] else: arc = rc.get_cal(ad, "processed_arc") # Take care of the case where there was no arc if arc is None: log.warning("Could not find an appropriate arc for %s" \ % (ad.filename)) adoutput_list.append(ad) continue else: arc = AstroData(arc) wavecal = arc["WAVECAL"] if wavecal is not None: # Remove old versions if ad["WAVECAL"] is not None: for wc in ad["WAVECAL"]: ad.remove((wc.extname(),wc.extver())) # Append new solution ad.append(wavecal) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=ad, keyword=timestamp_key) # Change the filename ad.filename = gt.filename_updater(adinput=ad, suffix=rc["suffix"], strip=True) adoutput_list.append(ad) else: log.warning("No wavelength solution found for %s" % ad.filename) adoutput_list.append(ad) # Report the list of output AstroData objects to the reduction # context rc.report_output(adoutput_list) yield rc
def subtractDark(self, rc): """ This primitive will subtract each SCI extension of the inputs by those of the corresponding dark. If the inputs contain VAR or DQ frames, those will also be updated accordingly due to the subtraction on the data. """ # Instantiate the log log = logutils.get_logger(__name__) # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "subtractDark", "starting")) # Define the keyword to be used for the time stamp for this primitive timestamp_key = self.timestamp_keys["subtractDark"] # Initialize the list of output AstroData objects adoutput_list = [] # Check for a user-supplied dark adinput = rc.get_inputs_as_astrodata() dark_param = rc["dark"] dark_dict = None if dark_param is not None: # The user supplied an input to the dark parameter if not isinstance(dark_param, list): dark_list = [dark_param] else: dark_list = dark_param # Convert filenames to AD instances if necessary tmp_list = [] for dark in dark_list: if type(dark) is not AstroData: dark = AstroData(dark) tmp_list.append(dark) dark_list = tmp_list dark_dict = gt.make_dict(key_list=adinput, value_list=dark_list) # Loop over each input AstroData object in the input list for ad in adinput: # Check whether the subtractDark primitive has been run previously if ad.phu_get_key_value(timestamp_key): log.warning("No changes will be made to %s, since it has " \ "already been processed by subtractDark" \ % (ad.filename)) # Append the input AstroData object to the list of output # AstroData objects without further processing adoutput_list.append(ad) continue # Retrieve the appropriate dark if dark_dict is not None: dark = dark_dict[ad] else: dark = rc.get_cal(ad, "processed_dark") # If there is no appropriate dark, there is no need to # subtract the dark if dark is None: log.warning("No changes will be made to %s, since no " \ "appropriate dark could be retrieved" \ % (ad.filename)) # Append the input AstroData object to the list of output # AstroData objects without further processing adoutput_list.append(ad) continue else: dark = AstroData(dark) # Subtract the dark from the input AstroData object log.fullinfo("Subtracting the dark (%s) from the input " \ "AstroData object %s" \ % (dark.filename, ad.filename)) ad = ad.sub(dark) # Record the dark file used ad.phu_set_key_value("DARKIM", os.path.basename(dark.filename), comment=self.keyword_comments["DARKIM"]) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=ad, keyword=timestamp_key) # Change the filename ad.filename = gt.filename_updater(adinput=ad, suffix=rc["suffix"], strip=True) # Append the output AstroData object to the list # of output AstroData objects adoutput_list.append(ad) # Report the list of output AstroData objects to the reduction # context rc.report_output(adoutput_list) yield rc
def divideByFlat(self, rc): """ This primitive will divide each SCI extension of the inputs by those of the corresponding flat. If the inputs contain VAR or DQ frames, those will also be updated accordingly due to the division on the data. """ # Instantiate the log log = logutils.get_logger(__name__) # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "divideByFlat", "starting")) # Define the keyword to be used for the time stamp for this primitive timestamp_key = self.timestamp_keys["divideByFlat"] # Initialize the list of output AstroData objects adoutput_list = [] # Check for a user-supplied flat adinput = rc.get_inputs_as_astrodata() flat_param = rc["flat"] flat_dict = None if flat_param is not None: # The user supplied an input to the flat parameter if not isinstance(flat_param, list): flat_list = [flat_param] else: flat_list = flat_param # Convert filenames to AD instances if necessary tmp_list = [] for flat in flat_list: if type(flat) is not AstroData: flat = AstroData(flat) tmp_list.append(flat) flat_list = tmp_list flat_dict = gt.make_dict(key_list=adinput, value_list=flat_list) # Loop over each input AstroData object in the input list for ad in adinput: # Check whether the divideByFlat primitive has been run previously if ad.phu_get_key_value(timestamp_key): log.warning("No changes will be made to %s, since it has " \ "already been processed by divideByFlat" \ % (ad.filename)) # Append the input AstroData object to the list of output # AstroData objects without further processing adoutput_list.append(ad) continue # Retrieve the appropriate flat if flat_dict is not None: flat = flat_dict[ad] else: flat = rc.get_cal(ad, "processed_flat") # If there is no appropriate flat, there is no need to divide by # the flat in QA context; in SQ context, raise an error if flat is None: if "qa" in rc.context: log.warning("No changes will be made to %s, since no " \ "appropriate flat could be retrieved" \ % (ad.filename)) # Append the input AstroData object to the list of output # AstroData objects without further processing adoutput_list.append(ad) continue else: raise Errors.PrimitiveError("No processed flat found "\ "for %s" % ad.filename) else: flat = AstroData(flat) # Check the inputs have matching filters, binning, and SCI shapes. try: gt.check_inputs_match(ad1=ad, ad2=flat) except Errors.ToolboxError: # If not, try to clip the flat frame to the size # of the science data # For a GMOS example, this allows a full frame flat to # be used for a CCD2-only science frame. flat = gt.clip_auxiliary_data( adinput=ad,aux=flat,aux_type="cal")[0] # Check again, but allow it to fail if they still don't match gt.check_inputs_match(ad1=ad, ad2=flat) # Divide the adinput by the flat log.fullinfo("Dividing the input AstroData object (%s) " \ "by this flat:\n%s" % (ad.filename, flat.filename)) ad = ad.div(flat) # Record the flat file used ad.phu_set_key_value("FLATIM", os.path.basename(flat.filename), comment=self.keyword_comments["FLATIM"]) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=ad, keyword=timestamp_key) # Change the filename ad.filename = gt.filename_updater(adinput=ad, suffix=rc["suffix"], strip=True) # Append the output AstroData object to the list # of output AstroData objects adoutput_list.append(ad) # Report the list of output AstroData objects to the reduction # context rc.report_output(adoutput_list) yield rc
def subtractFringe(self, rc): # Instantiate the log log = gemLog.getGeminiLog(logType=rc["logType"], logLevel=rc["logLevel"]) # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "subtractFringe", "starting")) # Define the keyword to be used for the time stamp for this primitive timestamp_key = self.timestamp_keys["subtractFringe"] # Initialize the list of output AstroData objects adoutput_list = [] # Check for a user-supplied fringe adinput = rc.get_inputs_as_astrodata() fringe_param = rc["fringe"] fringe_dict = None if fringe_param is not None: # The user supplied an input to the fringe parameter if not isinstance(fringe_param, list): fringe_list = [fringe_param] else: fringe_list = fringe_param # Convert filenames to AD instances if necessary tmp_list = [] for fringe in fringe_list: if type(fringe) is not AstroData: fringe = AstroData(fringe) tmp_list.append(fringe) fringe_list = tmp_list fringe_dict = gt.make_dict(key_list=adinput, value_list=fringe_list) # Loop over each input AstroData object in the input list for ad in adinput: # Check whether the subtractFringe primitive has been run # previously if ad.phu_get_key_value(timestamp_key): log.warning("No changes will be made to %s, since it has " \ "already been processed by subtractFringe" \ % (ad.filename)) # Append the input AstroData object to the list of output # AstroData objects without further processing adoutput_list.append(ad) continue # Retrieve the appropriate fringe if fringe_dict is not None: fringe = fringe_dict[ad] else: fringe = rc.get_cal(ad, "processed_fringe") # Take care of the case where there was no fringe if fringe is None: log.warning("Could not find an appropriate fringe for %s" \ % (ad.filename)) # Append the input to the output without further processing adoutput_list.append(ad) continue else: fringe = AstroData(fringe) # Check the inputs have matching filters, binning and SCI shapes. try: gt.check_inputs_match(ad1=ad, ad2=fringe) except Errors.ToolboxError: # If not, try to clip the fringe frame to the size of the # science data # For a GMOS example, this allows a full frame fringe to # be used for a CCD2-only science frame. fringe = gt.clip_auxiliary_data( adinput=ad, aux=fringe, aux_type="cal")[0] # Check again, but allow it to fail if they still don't match gt.check_inputs_match(ad1=ad, ad2=fringe) # Subtract the fringe from the science ad = ad.sub(fringe) # Record the fringe file used ad.phu_set_key_value("FRINGEIM", os.path.basename(fringe.filename), comment=self.keyword_comments["FRINGEIM"]) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=ad, keyword=timestamp_key) # Change the filename ad.filename = gt.filename_updater(adinput=ad, suffix=rc["suffix"], strip=True) # Append the output AstroData object to the list # of output AstroData objects adoutput_list.append(ad) # Report the list of output AstroData objects to the reduction context rc.report_output(adoutput_list) yield rc
def scaleFringeToScience(self, rc): """ This primitive will scale the fringes to their matching science data The fringes should be in the stream this primitive is called on, and the reference science frames should be loaded into the RC, as, eg. rc["science"] = adinput. There are two ways to find the value to scale fringes by: 1. If stats_scale is set to True, the equation: (letting science data = b (or B), and fringe = a (or A)) arrayB = where({where[SCIb < (SCIb.median+2.5*SCIb.std)]} > [SCIb.median-3*SCIb.std]) scale = arrayB.std / SCIa.std The section of the SCI arrays to use for calculating these statistics is the CCD2 SCI data excluding the outer 5% pixels on all 4 sides. Future enhancement: allow user to choose section 2. If stats_scale=False, then scale will be calculated using: exposure time of science / exposure time of fringe :param stats_scale: Use statistics to calculate the scale values, rather than exposure time :type stats_scale: Python boolean (True/False) """ # Instantiate the log log = gemLog.getGeminiLog(logType=rc["logType"], logLevel=rc["logLevel"]) # Log the standard "starting primitive" debug message log.debug(gt.log_message("primitive", "scaleFringeToScience", "starting")) # Define the keyword to be used for the time stamp for this primitive timestamp_key = self.timestamp_keys["scaleFringeToScience"] # Check for user-supplied science frames fringe = rc.get_inputs_as_astrodata() science_param = rc["science"] fringe_dict = None if science_param is not None: # The user supplied an input to the science parameter if not isinstance(science_param, list): science_list = [science_param] else: science_list = science_param # If there is one fringe and multiple science frames, # the fringe must be deepcopied to allow it to be # scaled separately for each frame if len(fringe)==1 and len(science_list)>1: fringe = [deepcopy(fringe[0]) for img in science_list] # Convert filenames to AD instances if necessary tmp_list = [] for science in science_list: if type(science) is not AstroData: science = AstroData(science) tmp_list.append(science) science_list = tmp_list fringe_dict = gt.make_dict(key_list=science_list, value_list=fringe) fringe_output = [] else: log.warning("No science frames specified; no scaling will be done") science_list = [] fringe_output = fringe # Loop over each AstroData object in the science list for ad in science_list: # Retrieve the appropriate fringe fringe = fringe_dict[ad] # Check the inputs have matching filters, binning and SCI shapes. try: gt.check_inputs_match(ad1=ad, ad2=fringe) except Errors.ToolboxError: # If not, try to clip the fringe frame to the size of the # science data # For a GMOS example, this allows a full frame fringe to # be used for a CCD2-only science frame. fringe = gt.clip_auxiliary_data( adinput=ad, aux=fringe, aux_type="cal")[0] # Check again, but allow it to fail if they still don't match gt.check_inputs_match(ad1=ad, ad2=fringe) # Check whether statistics should be used stats_scale = rc["stats_scale"] # Calculate the scale value scale = 1.0 if not stats_scale: # Use the exposure times to calculate the scale log.fullinfo("Using exposure times to calculate the scaling"+ " factor") try: scale = ad.exposure_time() / fringe.exposure_time() except: raise Errors.InputError("Could not get exposure times " + "for %s, %s. Try stats_scale=True" % (ad.filename,fringe.filename)) else: # Use statistics to calculate the scaling factor log.fullinfo("Using statistics to calculate the " + "scaling factor") # Deepcopy the input so it can be manipulated without # affecting the original statsad = deepcopy(ad) statsfringe = deepcopy(fringe) # Trim off any overscan region still present statsad,statsfringe = gt.trim_to_data_section([statsad, statsfringe]) # Check the number of science extensions; if more than # one, use CCD2 data only nsciext = statsad.count_exts("SCI") if nsciext>1: # Get the CCD numbers and ordering information # corresponding to each extension log.fullinfo("Trimming data to data section to remove "\ "overscan region") sci_info,frng_info = gt.array_information([statsad, statsfringe]) # Pull out CCD2 data scidata = [] frngdata = [] dqdata = [] for i in range(nsciext): # Get the next extension in physical order sciext = statsad["SCI",sci_info["amps_order"][i]] frngext = statsfringe["SCI",frng_info["amps_order"][i]] # Check to see if it is on CCD2; if so, keep it if sci_info[ "array_number"][("SCI",sciext.extver())]==2: scidata.append(sciext.data) dqext = statsad["DQ",sci_info["amps_order"][i]] maskext = statsad["OBJMASK", sci_info["amps_order"][i]] if dqext is not None and maskext is not None: dqdata.append(dqext.data | maskext.data) elif dqext is not None: dqdata.append(dqext.data) elif maskext is not None: dqdata.append(maskext.data) if frng_info[ "array_number"][("SCI",frngext.extver())]==2: frngdata.append(frngext.data) # Stack data if necessary if len(scidata)>1: scidata = np.hstack(scidata) frngdata = np.hstack(frngdata) else: scidata = scidata[0] frngdata = frngdata[0] if len(dqdata)>0: if len(dqdata)>1: dqdata = np.hstack(dqdata) else: dqdata = dqdata[0] else: dqdata = None else: scidata = statsad["SCI"].data frngdata = statsfringe["SCI"].data dqext = statsad["DQ"] maskext = statsad["OBJMASK"] if dqext is not None and maskext is not None: dqdata = dqext.data | maskext.data elif dqext is not None: dqdata = dqext.data elif maskext is not None: dqdata = maskext.data else: dqdata = None if dqdata is not None: # Replace any DQ-flagged data with the median value smed = np.median(scidata[dqdata==0]) scidata = np.where(dqdata!=0,smed,scidata) # Calculate the maximum and minimum in a box centered on # each data point. The local depth of the fringe is # max - min. The overall fringe strength is the median # of the local fringe depths. # Width of the box is binning and # filter dependent, determined by experimentation # Results don't seem to depend heavily on the box size if ad.filter_name(pretty=True).as_pytype=="i": size = 20 else: size = 40 size /= ad.detector_x_bin().as_pytype() # Use ndimage maximum_filter and minimum_filter to # get the local maxima and minima import scipy.ndimage as ndimage sci_max = ndimage.filters.maximum_filter(scidata,size) sci_min = ndimage.filters.minimum_filter(scidata,size) # Take off 5% of the width as a border xborder = int(0.05 * scidata.shape[1]) yborder = int(0.05 * scidata.shape[0]) if xborder<20: xborder = 20 if yborder<20: yborder = 20 sci_max = sci_max[yborder:-yborder,xborder:-xborder] sci_min = sci_min[yborder:-yborder,xborder:-xborder] # Take the median difference sci_df = np.median(sci_max - sci_min) # Do the same for the fringe frn_max = ndimage.filters.maximum_filter(frngdata,size) frn_min = ndimage.filters.minimum_filter(frngdata,size) frn_max = frn_max[yborder:-yborder,xborder:-xborder] frn_min = frn_min[yborder:-yborder,xborder:-xborder] frn_df = np.median(frn_max - frn_min) # Scale factor # This tends to overestimate the factor, but it is # at least in the right ballpark, unlike the estimation # used in girmfringe (masked_sci.std/fringe.std) scale = sci_df / frn_df log.fullinfo("Scale factor found = "+str(scale)) # Use mult from the arith toolbox to perform the scaling of # the fringe frame scaled_fringe = fringe.mult(scale) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=scaled_fringe, keyword=timestamp_key) # Change the filename scaled_fringe.filename = gt.filename_updater( adinput=ad, suffix=rc["suffix"], strip=True) fringe_output.append(scaled_fringe) # Report the list of output AstroData objects to the reduction context rc.report_output(fringe_output) yield rc
def attachWavelengthSolution(self, rc): # Instantiate the log log = logutils.get_logger(__name__) # Define the keyword to be used for the time stamp timestamp_key = self.timestamp_keys["attachWavelengthSolution"] # Log the standard "starting primitive" debug message log.debug( gt.log_message("primitive", "attachWavelengthSolution", "starting")) # Initialize the list of output AstroData objects adoutput_list = [] # Check for a user-supplied arc adinput = rc.get_inputs_as_astrodata() arc_param = rc["arc"] arc_dict = None if arc_param is not None: # The user supplied an input to the arc parameter if not isinstance(arc_param, list): arc_list = [arc_param] else: arc_list = arc_param # Convert filenames to AD instances if necessary tmp_list = [] for arc in arc_list: if type(arc) is not AstroData: arc = AstroData(arc) tmp_list.append(arc) arc_list = tmp_list arc_dict = gt.make_dict(key_list=adinput, value_list=arc_list) for ad in adinput: if arc_dict is not None: arc = arc_dict[ad] else: arc = rc.get_cal(ad, "processed_arc") # Take care of the case where there was no arc if arc is None: log.warning("Could not find an appropriate arc for %s" \ % (ad.filename)) adoutput_list.append(ad) continue else: arc = AstroData(arc) wavecal = arc["WAVECAL"] if wavecal is not None: # Remove old versions if ad["WAVECAL"] is not None: for wc in ad["WAVECAL"]: ad.remove((wc.extname(), wc.extver())) # Append new solution ad.append(wavecal) # Add the appropriate time stamps to the PHU gt.mark_history(adinput=ad, primname=self.myself(), keyword=timestamp_key) # Change the filename ad.filename = gt.filename_updater(adinput=ad, suffix=rc["suffix"], strip=True) adoutput_list.append(ad) else: log.warning("No wavelength solution found for %s" % ad.filename) adoutput_list.append(ad) # Report the list of output AstroData objects to the reduction # context rc.report_output(adoutput_list) yield rc