def make_fixedslit_bkg(self): """Add a background to a MIR_lrs-fixedslit observation""" for product in self['products']: members = product['members'] # Split out the science exposures science_exps = [ member for member in members if member['exptype'] == 'science' ] # Create new members for each science exposure in the association, # using the the base name + _x1d as background. results = [] # Loop over all science exposures in the association for science_exp in science_exps: sci_name = science_exp['expname'] science_exp['expname'] = sci_name # Construct the name for the background file bkg_name = remove_suffix( splitext(split(science_exp['expname'])[1])[0])[0] bkg_name = bkg_name + '_x1d.fits' now_background = Member(science_exp) now_background['expname'] = bkg_name now_background['exptype'] = 'background' # Add the background file to the association table members.append(now_background) if self.is_valid: results.append(self) return results
def make_nod_asns(self): """Make background nod Associations For observing modes, such as NIRSpec MSA, exposures can be nodded, such that the object is in a different position in the slitlet. The association creation simply groups these all together as a single association, all exposures marked as `science`. When complete, this method will create separate associations each exposure becoming the single science exposure, and the other exposures then become `background`. Returns ------- associations : [association[, ...]] List of new associations to be used in place of the current one. """ for product in self['products']: members = product['members'] # Split out the science vs. non-science # The non-science exposures will get attached # to every resulting association. science_exps = [ member for member in members if member['exptype'] == 'science' ] nonscience_exps = [ member for member in members if member['exptype'] != 'science' ] # Create new associations for each science, using # the other science as background. results = [] for science_exp in science_exps: asn = copy.deepcopy(self) asn.data['products'] = None product_name = remove_suffix( splitext(split(science_exp['expname'])[1])[0])[0] asn.new_product(product_name) new_members = asn.current_product['members'] new_members.append(science_exp) for other_science in science_exps: if other_science['expname'] != science_exp['expname']: now_background = Member(other_science) now_background['exptype'] = 'background' new_members.append(now_background) new_members += nonscience_exps if asn.is_valid: results.append(asn) return results
def _add_items(self, items, product_name=None, with_exptype=False, **kwargs): """ Force adding items to the association Parameters ---------- items : [object[, ...]] A list of items to make members of the association. product_name : str or None The name of the product to add the items to. If the product does not already exist, it will be created. If None, the default DMS Level3 naming conventions will be attempted. with_exptype : bool If True, each item is expected to be a 2-tuple with the first element being the item to add as `expname` and the second items is the `exptype` kwargs : dict Allows other keyword arguments used by other subclasses. Notes ----- This is a low-level shortcut into adding members, such as file names, to an association. All defined shortcuts and other initializations are by-passed, resulting in a potentially unusable association. """ if product_name is None: raise AssociationNotValidError( 'Product name needs to be specified' ) self.new_product(product_name) members = self.current_product['members'] for item in items: exptype = 'science' if with_exptype: item, exptype = item member = Member( { 'expname': item, 'exptype': exptype }, item=item ) self.update_validity(member) members.append(member) self.sequence = next(self._sequence)
def make_fixedslit_bkg(self): """Add a background to a MIR_lrs-fixedslit observation""" # check to see if these are nodded backgrounds, if they are setup # the background members, otherwise return the original association # to test for the string 'nod' we need to copy and pop the value out of the set if 'nod' not in self.constraints['patttype_spectarg'].found_values.copy().pop(): results = [] results.append(self) return results for product in self['products']: members = product['members'] # Split out the science exposures science_exps = [ member for member in members if member['exptype'] == 'science' ] # if there is only one science observation it cannot be the background # return with original association. if len(science_exps) < 2: return results # Create new members for each science exposure in the association, # using the the base name + _x1d as background. results = [] # Loop over all science exposures in the association for science_exp in science_exps: sci_name = science_exp['expname'] science_exp['expname'] = sci_name # Construct the name for the background file bkg_name = remove_suffix( splitext(split(science_exp['expname'])[1])[0])[0] bkg_name = bkg_name+'_x1d.fits' now_background = Member(science_exp) now_background['expname'] = bkg_name now_background['exptype'] = 'background' # Add the background file to the association table members.append(now_background) if self.is_valid: results.append(self) return results
def make_member(self, item): """Create a member from the item Parameters ---------- item : dict The item to create member from. Returns ------- member : Member The member """ # Set exposure error status. try: exposerr = item['exposerr'] except KeyError: exposerr = None # Create the member. # `is_item_tso` is used to determine whether the name should # represent the integrations form of the data. # Though coronagraphic data is not TSO, # it does remain in the separate integrations. member = Member( { 'expname': Utility.rename_to_level2a( item['filename'], use_integrations=self.is_item_tso( item, other_exp_types=CORON_EXP_TYPES), ), 'exptype': self.get_exposure_type(item), 'exposerr': exposerr, }, item=item) return member
def make_member(self, item): """Create a member from the item Parameters ---------- item : dict The item to create member from. Returns ------- member : Member The member """ try: exposerr = item['exposerr'] except KeyError: exposerr = None # Get exposure type exptype = self.get_exposure_type(item) # Determine expected member name expname = Utility.rename_to_level2( item['filename'], exp_type=item['exp_type'], is_tso=self.is_item_tso(item, other_exp_types=CORON_EXP_TYPES), member_exptype=exptype ) member = Member( { 'expname': expname, 'exptype': exptype, 'exposerr': exposerr, 'asn_candidate': item['asn_candidate'] }, item=item ) return member
def _add_items(self, items, meta=None, product_name_func=None, acid='o999', **kwargs): """Force adding items to the association Parameters ---------- items : [object[, ...]] A list of items to make members of the association. meta : dict A dict to be merged into the association meta information. The following are suggested to be assigned: - `asn_type` The type of association. - `asn_rule` The rule which created this association. - `asn_pool` The pool from which the exposures came from - `program` Originating observing program product_name_func : func Used if product name is 'undefined' using the class's procedures. acid : str The association candidate id to use. Since Level2 associations require it, one must be specified. Notes ----- This is a low-level shortcut into adding members, such as file names, to an association. All defined shortcuts and other initializations are by-passed, resulting in a potentially unusable association. `product_name_func` is used to define the product names instead of the default methods. The call signature is: product_name_func(item, idx) where `item` is each item being added and `idx` is the count of items. """ if meta is None: meta = {} # Setup association candidate. if acid.startswith('o'): ac_type = 'observation' elif acid.startswith('c'): ac_type = 'background' else: raise ValueError( 'Invalid association id specified: "{}"' '\n\tMust be of form "oXXX" or "c1XXX"'.format(acid) ) self._acid = ACID((acid, ac_type)) # set the default exptype exptype = 'science' for idx, item in enumerate(items, start=1): self.new_product() members = self.current_product['members'] if isinstance(item, tuple): expname = item[0] else: expname = item # check to see if kwargs are passed and if exptype is given if kwargs: if 'with_exptype' in kwargs: if item[1]: exptype = item[1] else: exptype = 'science' member = Member({ 'expname': expname, 'exptype': exptype }, item=item) members.append(member) self.update_validity(member) self.update_asn() # If a product name function is given, attempt # to use. if product_name_func is not None: try: self.current_product['name'] = product_name_func(item, idx) except Exception: logger.debug( 'Attempted use of product_name_func failed.' ' Default product name used.' ) self.data.update(meta) self.sequence = next(self._sequence)
def add_catalog_members(self): """Add catalog and direct image member based on direct image members""" directs = self.members_by_type('direct_image') if not directs: raise AssociationNotValidError( '{} has no required direct image exposures'.format( self.__class__.__name__ ) ) sciences = self.members_by_type('science') if not sciences: raise AssociationNotValidError( '{} has no required science exposure'.format( self.__class__.__name__ ) ) science = sciences[0] # Get the exposure sequence for the science. Then, find # the direct image greater than but closest to this value. closest = directs[0] # If the search fails, just use the first. try: expspcin = int(getattr_from_list(science.item, ['expspcin'], _EMPTY)[1]) except KeyError: # If exposure sequence cannot be determined, just fall through. logger.debug('Science exposure %s has no EXPSPCIN defined.', science) else: min_diff = 9999 # Initialize to an invalid value. for direct in directs: # For NIRCam, only consider direct images from the same channel # as the grism image if direct.item['exp_type'] == 'nrc_image': science_channel = getattr_from_list(science.item, ['channel'], _EMPTY)[1] direct_channel = getattr_from_list(direct.item, ['channel'], _EMPTY)[1] if direct_channel != science_channel: continue # For NIRISS, only consider direct images with the same PUPIL value if direct.item['exp_type'] == 'nis_image': science_pupil = getattr_from_list(science.item, ['pupil'], _EMPTY)[1] direct_pupil = getattr_from_list(direct.item, ['pupil'], _EMPTY)[1] if direct_pupil != science_pupil: continue try: direct_expspcin = int(getattr_from_list( direct.item, ['expspcin'], _EMPTY )[1]) except KeyError: # Try the next one. logger.debug('Direct image %s has no EXPSPCIN defined.', direct) continue diff = direct_expspcin - expspcin if diff < min_diff and diff > 0: min_diff = diff closest = direct # Note the selected direct image. Used in `Asn_Lv2WFSS._get_opt_element` self.direct_image = closest # Remove all direct images from the association. members = self.current_product['members'] direct_idxs = [ idx for idx, member in enumerate(members) if member['exptype'] == 'direct_image' ] deque(( list.pop(members, idx) for idx in sorted(direct_idxs, reverse=True) )) # Add the Level3 catalog, direct image, and segmentation map members lv3_direct_image_root = DMS_Level3_Base._dms_product_name(self) members.append( Member({ 'expname': lv3_direct_image_root + '_i2d.fits', 'exptype': 'direct_image' }) ) members.append( Member({ 'expname': lv3_direct_image_root + '_cat.ecsv', 'exptype': 'sourcecat' }) ) members.append( Member({ 'expname': lv3_direct_image_root + '_segm.fits', 'exptype': 'segmap' }) )