def GetRNG(config, base, logger=None, tag=''): """Get the appropriate current rng according to whatever the current index_key is. If a logger is provided, then it will emit a warning if there is no current rng setup. @param config The configuration dict for the current item being worked on. @param base The base configuration dict. @param logger If given, a logger object to log progress. [default: None] @param tag If given, an appropriate name for the current item to use in the warning message. [default: ''] @returns either the appropriate rng for the current index_key or None """ logger = LoggerWrapper(logger) index, index_key = GetIndex(config, base) logger.debug("GetRNG for %s: %s", index_key, index) rng_num = config.get('rng_num', 0) if rng_num != 0: if int(rng_num) != rng_num: raise galsim.GalSimConfigValueError("rng_num must be an integer", rng_num) rngs = base.get(index_key + '_rngs', None) if rngs is None: raise galsim.GalSimConfigError( "rng_num is only allowed when image.random_seed is a list") if rng_num < 0 or rng_num > len(rngs): raise galsim.GalSimConfigError( "rng_num is invalid. Must be in [0,%d]" % (len(rngs))) rng = rngs[int(rng_num)] else: rng = base.get(index_key + '_rng', None) if rng is None: logger.debug("No index_key_rng. Use base[rng]") rng = base.get('rng', None) if rng is None and logger: # Only report the warning the first time. rng_tag = tag + '_reported_no_rng' if rng_tag not in base: base[rng_tag] = True logger.warning( "No base['rng'] available for %s. Using /dev/urandom.", tag) return rng
def SetupConfigImageNum(config, image_num, obj_num, logger=None): """Do the basic setup of the config dict at the image processing level. Includes: - Set config['image_num'] = image_num - Set config['obj_num'] = obj_num - Set config['index_key'] = 'image_num' - Make sure config['image'] exists - Set default config['image']['type'] to 'Single' if not specified - Check that the specified image type is valid. Parameters: config: The configuration dict. image_num: The current image number. obj_num: The first object number in the image. logger: If given, a logger object to log progress. [default: None] """ logger = galsim.config.LoggerWrapper(logger) config['image_num'] = image_num config['obj_num'] = obj_num config['index_key'] = 'image_num' # Make config['image'] exist if it doesn't yet. if 'image' not in config: config['image'] = {} image = config['image'] if not isinstance(image, dict): raise galsim.GalSimConfigError("config.image is not a dict.") if 'file_num' not in config: config['file_num'] = 0 if 'type' not in image: image['type'] = 'Single' image_type = image['type'] if image_type not in valid_image_types: raise galsim.GalSimConfigValueError("Invalid image.type.", image_type, valid_image_types) # In case this hasn't been done yet. galsim.config.SetupInput(config, logger) # Build the rng to use at the image level. seed = galsim.config.SetupConfigRNG(config, logger=logger) logger.debug('image %d: seed = %d', image_num, seed)
def GetNObjForImage(config, image_num): """ Get the number of objects that will be made for the image number image_num based on the information in the config dict. Parameters: config: The configuration dict. image_num: The current image number. Returns: the number of objects """ image = config.get('image', {}) image_type = image.get('type', 'Single') if image_type not in valid_image_types: raise galsim.GalSimConfigValueError("Invalid image.type.", image_type, valid_image_types) return valid_image_types[image_type].getNObj(image, config, image_num)
def GetNObjForFile(config, file_num, image_num): """ Get the number of objects that will be made for each image built as part of the file file_num, which starts at image number image_num, based on the information in the config dict. @param config The configuration dict. @param file_num The current file number. @param image_num The current image number. @returns a list of the number of objects in each image [ nobj0, nobj1, nobj2, ... ] """ output = config.get('output', {}) output_type = output.get('type', 'Fits') if output_type not in valid_output_types: raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, valid_output_types) return valid_output_types[output_type].getNObjPerImage( output, config, file_num, image_num)
def GetNImagesForFile(config, file_num): """ Get the number of images that will be made for the file number file_num, based on the information in the config dict. Parameters: config: The configuration dict. file_num: The current file number. Returns: the number of images """ output = config.get('output', {}) output_type = output.get('type', 'Fits') if output_type not in valid_output_types: raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, valid_output_types) return valid_output_types[output_type].getNImages(output, config, file_num)
def AddExtraOutputHDUs(config, main_data, logger=None): """Write the extra output objects to either HDUS or images as appropriate and add them to the existing data. This gets run at the end of the functions for building the regular output files. Note: the extra items must have hdu numbers ranging continuously (in any order) starting at len(data). Typically first = 1, since the main image is the primary HDU, numbered 0. @param config The configuration dict. @param main_data The main file data as a list of images. Usually just [image] where image is the primary image to be written to the output file. @param logger If given, a logger object to log progress. [default: None] @returns data with additional hdus added """ output = config['output'] hdus = {} for key in (k for k in valid_extra_outputs.keys() if k in output): field = output[key] if 'hdu' in field: hdu = galsim.config.ParseValue(field,'hdu',config,int)[0] else: # pragma: no cover (it is covered, but codecov wrongly thinks it isn't.) # If no hdu, then probably writing to file continue if hdu <= 0 or hdu in hdus: raise galsim.GalSimConfigValueError("hdu is invalid or a duplicate.",hdu) builder = config['extra_builder'][key] # Do any final processing that needs to happen. builder.ensureFinalized(field, config, main_data, logger) # Build the HDU for this output object. hdus[hdu] = builder.writeHdu(field,config,logger) first = len(main_data) for h in range(first,len(hdus)+first): if h not in hdus: raise galsim.GalSimConfigError("Cannot skip hdus. No output found for hdu %d"%h) # Turn hdus into a list (in order) hdulist = [ hdus[k] for k in range(first,len(hdus)+first) ] return main_data + hdulist
def AddNoise(config, im, current_var=0., logger=None): """ Add noise to an image according to the noise specifications in the noise dict. Parameters: config: The configuration dict im: The image onto which to add the noise current_var: The current noise variance present in the image already [default: 0] logger: If given, a logger object to log progress. [default: None] """ logger = galsim.config.LoggerWrapper(logger) if 'noise' in config['image']: noise = config['image']['noise'] else: # No noise. return if not isinstance(noise, dict): raise galsim.GalSimConfigError("image.noise is not a dict.") # Default is Poisson noise_type = noise.get('type', 'Poisson') if noise_type not in valid_noise_types: raise galsim.GalSimConfigValueError("Invalid noise.type.", noise_type, valid_noise_types) # We need to use image_num for the index_key, but if we are running this from the stamp # building phase, then we want to use obj_num_rng for the noise rng. So get the rng now # before we change config['index_key']. index, orig_index_key = galsim.config.GetIndex(noise, config) rng = galsim.config.GetRNG(noise, config) # This makes sure draw_method is properly copied over and given a default value. galsim.config.stamp.SetupConfigObjNum(config, config.get('obj_num', 0), logger) draw_method = galsim.config.GetCurrentValue('draw_method', config['stamp'], str, config) builder = valid_noise_types[noise_type] config['index_key'] = 'image_num' var = builder.addNoise(noise, config, im, rng, current_var, draw_method, logger) config['index_key'] = orig_index_key return var
def setup(self, config, base, xsize, ysize, ignore, logger): """Do the initialization and setup for the Ring type. Most of the work is done by SetupBasic, but we do need to check that the required parameters are present, and also that no additional parameters are present. Parameters: config: The configuration dict for the stamp field. base: The base configuration dict. xsize: The xsize of the image to build (if known). ysize: The ysize of the image to build (if known). ignore: A list of parameters that are allowed to be in config that we can ignore here. i.e. it won't be an error if these parameters are present. logger: If given, a logger object to log progress. Returns: xsize, ysize, image_pos, world_pos """ req = { 'num' : int } opt = { 'full_rotation' : galsim.Angle , 'index' : int } # Ignore the transformation specifications that are allowed in stamp for Ring types. ignore = ignore + [ 'dilate', 'dilation', 'ellip', 'rotate', 'rotation', 'scale_flux', 'magnify', 'magnification', 'shear', 'shift' ] params = galsim.config.GetAllParams(config, base, req=req, opt=opt, ignore=ignore)[0] num = params['num'] if num <= 0: raise galsim.GalSimConfigValueError("Attribute num for gal.type == Ring must be > 0", num) # Setup the indexing sequence if it hasn't been specified using the number of items. galsim.config.SetDefaultIndex(config, num) # Set the default full_rotation to pi radians if 'full_rotation' not in params: import math config['full_rotation'] = math.pi * galsim.radians # Now go on and do the base class setup. ignore = ignore + list(req) + list(opt) return super(RingBuilder, self).setup(config, base, xsize, ysize, ignore, logger)
def SetupConfigFileNum(config, file_num, image_num, obj_num, logger=None): """Do the basic setup of the config dict at the file processing level. Includes: - Set config['file_num'] = file_num - Set config['image_num'] = image_num - Set config['obj_num'] = obj_num - Set config['index_key'] = 'file_num' - Set config['start_image_num'] = image_num - Set config['start_obj_num'] = obj_num - Make sure config['output'] exists - Set default config['output']['type'] to 'Fits' if not specified - Check that the specified output type is valid. Parameters: config: A configuration dict. file_num: The current file_num. (If file_num=None, then don't set file_num or start_obj_num items in the config dict.) image_num: The current image_num. obj_num: The current obj_num. logger: If given, a logger object to log progress. [default: None] """ logger = galsim.config.LoggerWrapper(logger) config['file_num'] = file_num config['start_obj_num'] = obj_num config['start_image_num'] = image_num config['image_num'] = image_num config['obj_num'] = obj_num config['index_key'] = 'file_num' if 'output' not in config: config['output'] = {} if 'type' not in config['output']: config['output']['type'] = 'Fits' # Check that the type is valid output_type = config['output']['type'] if output_type not in valid_output_types: raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, valid_output_types)
def _GenerateFromPowerSpectrumMagnification(config, base, value_type): """@brief Return a magnification calculated from a PowerSpectrum object. """ power_spectrum = galsim.config.GetInputObj('power_spectrum', config, base, 'PowerSpectrumMagnification') logger = power_spectrum.logger if 'world_pos' not in base: raise galsim.GalSimConfigError( "PowerSpectrumMagnification requested, but no position defined.") pos = base['world_pos'] opt = {'max_mu': float, 'num': int} kwargs = galsim.config.GetAllParams(config, base, opt=opt)[0] with warnings.catch_warnings(record=True) as w: mu = power_spectrum.getMagnification(pos) if len(w) > 0: log_level = (logging.WARNING if 'current_image' in base and base['current_image'].outer_bounds.includes(pos) else logging.DEBUG) for ww in w: logger.log(log_level, 'obj %d: %s', base['obj_num'], ww.message) max_mu = kwargs.get('max_mu', 25.) if not max_mu > 0.: raise galsim.GalSimConfigValueError( "Invalid max_mu for type = PowerSpectrumMagnification (must be > 0)", max_mu) if mu < 0 or mu > max_mu: logger.warning( 'obj %d: Warning: PowerSpectrum mu = %f means strong lensing. ' % (base['obj_num'], mu) + 'Using mu=%f' % max_mu) mu = max_mu logger.debug('obj %d: PowerSpectrum mu = %s', base['obj_num'], mu) return mu, False
def AddNoiseVariance(config, im, include_obj_var=False, logger=None): """ Add the noise variance to an image according to the noise specifications in the noise dict. Typically, this is used for building a weight map, which is typically the inverse variance. Parameters: config: The configuration dict im: The image onto which to add the variance values include_obj_var: Whether to add the variance from the object photons for noise models that have a component based on the number of photons. Note: if this is True, the returned variance will not include this contribution to the noise variance. [default: False] logger: If given, a logger object to log progress. [default: None] Returns: the variance in the image """ logger = galsim.config.LoggerWrapper(logger) if 'noise' in config['image']: noise = config['image']['noise'] else: # No noise. return if not isinstance(noise, dict): raise galsim.GalSimConfigError("image.noise is not a dict.") noise_type = noise.get('type', 'Poisson') if noise_type not in valid_noise_types: raise galsim.GalSimConfigValueError("Invalid noise.type.", noise_type, valid_noise_types) index, orig_index_key = galsim.config.GetIndex(noise, config) config['index_key'] = 'image_num' builder = valid_noise_types[noise_type] builder.addNoiseVariance(noise, config, im, include_obj_var, logger) config['index_key'] = orig_index_key
def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): """ Build a single stamp image using the given config file Parameters: config: A configuration dict. obj_num: If given, the current obj_num [default: 0] xsize: The xsize of the stamp to build (if known). [default: 0] ysize: The ysize of the stamp to build (if known). [default: 0] do_noise: Whether to add noise to the image (according to config['noise']). [default: True] logger: If given, a logger object to log progress. [default: None] Returns: the tuple (image, current_var) """ logger = galsim.config.LoggerWrapper(logger) SetupConfigObjNum(config, obj_num, logger) stamp = config['stamp'] stamp_type = stamp['type'] if stamp_type not in valid_stamp_types: raise galsim.GalSimConfigValueError("Invalid stamp.type.", stamp_type, valid_stamp_types) builder = valid_stamp_types[stamp_type] # Add 1 to the seed here so the first object has a different rng than the file or image. seed = galsim.config.SetupConfigRNG(config, seed_offset=1, logger=logger) logger.debug('obj %d: seed = %d', obj_num, seed) if 'retry_failures' in stamp: ntries = galsim.config.ParseValue(stamp, 'retry_failures', config, int)[0] # This is how many _re_-tries. Do at least 1, so ntries is 1 more than this. ntries = ntries + 1 elif ('reject' in stamp or 'min_flux_frac' in stamp or 'min_snr' in stamp or 'max_snr' in stamp): # Still impose a maximum number of tries to prevent infinite loops. ntries = 20 else: ntries = 1 itry = 0 while True: itry += 1 # itry increases from 1..ntries at which point we just reraise the exception. # The rest of the stamp generation stage is wrapped in a try/except block. # If we catch an exception, we continue the for loop to try again. # On the last time through, we reraise any exception caught. # If no exception is thrown, we simply break the loop and return. try: # Do the necessary initial setup for this stamp type. xsize, ysize, image_pos, world_pos = builder.setup( stamp, config, xsize, ysize, stamp_ignore, logger) # Save these values for possible use in Evals or other modules SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos, logger) stamp_center = config['stamp_center'] if xsize: logger.debug('obj %d: xsize,ysize = %s,%s', obj_num, xsize, ysize) if image_pos: logger.debug('obj %d: image_pos = %s', obj_num, image_pos) if world_pos: logger.debug('obj %d: world_pos = %s', obj_num, world_pos) if stamp_center: logger.debug('obj %d: stamp_center = %s', obj_num, stamp_center) # Get the global gsparams kwargs. Individual objects can add to this. gsparams = {} if 'gsparams' in stamp: gsparams = galsim.config.UpdateGSParams( gsparams, stamp['gsparams'], config) # Note: Skip is different from Reject. # Skip means we return None for this stamp image and continue on. # Reject means we retry this object using the same obj_num. # This has implications for the total number of objects as well as # things like ring tests that rely on objects being made in pairs. # # Skip is also different from prof = None. # If prof is None, then the user indicated that no object should be # drawn on this stamp, but that a noise image is still desired. if 'skip' in stamp: skip = galsim.config.ParseValue(stamp, 'skip', config, bool)[0] logger.info('Skipping object %d', obj_num) else: skip = False if not skip: try: psf = galsim.config.BuildGSObject(config, 'psf', gsparams=gsparams, logger=logger)[0] prof = builder.buildProfile(stamp, config, psf, gsparams, logger) except galsim.config.gsobject.SkipThisObject as e: logger.debug('obj %d: Caught SkipThisObject: e = %s', obj_num, e.msg) logger.info('Skipping object %d', obj_num) skip = True im = builder.makeStamp(stamp, config, xsize, ysize, logger) if not skip: method = galsim.config.ParseValue(stamp, 'draw_method', config, str)[0] if method not in valid_draw_methods: raise galsim.GalSimConfigValueError( "Invalid draw_method.", method, valid_draw_methods) offset = config['stamp_offset'] if 'offset' in stamp: offset += galsim.config.ParseValue(stamp, 'offset', config, galsim.PositionD)[0] logger.debug('obj %d: stamp_offset = %s, offset = %s', obj_num, config['stamp_offset'], offset) skip = builder.updateSkip(prof, im, method, offset, stamp, config, logger) if not skip: im = builder.draw(prof, im, method, offset, stamp, config, logger) scale_factor = builder.getSNRScale(im, stamp, config, logger) im, prof = builder.applySNRScale(im, prof, scale_factor, method, logger) # Set the origin appropriately if im is None: # Note: im might be None here if the stamp size isn't given and skip==True. pass elif stamp_center: im.setCenter(stamp_center) else: im.setOrigin(config.get('image_origin', galsim.PositionI(1, 1))) # Store the current stamp in the base-level config for reference config['current_stamp'] = im # This is also information that the weight image calculation needs config['do_noise_in_stamps'] = do_noise # Check if this object should be rejected. if not skip: reject = builder.reject(stamp, config, prof, psf, im, logger) if reject: if itry < ntries: logger.warning( 'Object %d: Rejecting this object and rebuilding', obj_num) builder.reset(config, logger) continue else: raise galsim.GalSimConfigError( "Rejected an object %d times. If this is expected, " "you should specify a larger stamp.retry_failures." % (ntries)) galsim.config.ProcessExtraOutputsForStamp(config, skip, logger) # We always need to do the whiten step here in the stamp processing if not skip: current_var = builder.whiten(prof, im, stamp, config, logger) if current_var != 0.: logger.debug( 'obj %d: whitening noise brought current var to %f', config['obj_num'], current_var) else: current_var = 0. # Sometimes, depending on the image type, we go on to do the rest of the noise as well. if do_noise and not skip: im, current_var = builder.addNoise(stamp, config, im, skip, current_var, logger) return im, current_var except KeyboardInterrupt: raise except Exception as e: if itry >= ntries: # Then this was the last try. Just re-raise the exception. logger.info('Object %d: Caught exception %s', obj_num, str(e)) if ntries > 1: logger.error( 'Object %d: Too many exceptions/rejections for this object. Aborting.', obj_num) raise else: logger.info('Object %d: Caught exception %s', obj_num, str(e)) logger.info('This is try %d/%d, so trying again.', itry, ntries) import traceback tr = traceback.format_exc() logger.debug('obj %d: Traceback = %s', obj_num, tr) # Need to remove the "current"s from the config dict. Otherwise, # the value generators will do a quick return with the cached value. builder.reset(config, logger) continue
def BuildWCS(config, key, base, logger=None): """Read the wcs parameters from config[key] and return a constructed wcs object. @param config A dict with the configuration information. (usually base['image']) @param key The key name in config indicating which object to build. @param base The base dict of the configuration. @param logger Optionally, provide a logger for logging debug statements. [default: None] @returns a BaseWCS instance """ logger = galsim.config.LoggerWrapper(logger) logger.debug('image %d: Start BuildWCS key = %s', base.get('image_num', 0), key) try: param = config[key] except KeyError: # Default if no wcs is to use PixelScale if 'pixel_scale' in config: scale = galsim.config.ParseValue(config, 'pixel_scale', base, float)[0] else: scale = 1.0 return galsim.PixelScale(scale) # Check for direct value, else get the wcs type if isinstance(param, galsim.BaseWCS): return param elif param == str(param) and (param[0] == '$' or param[0] == '@'): return galsim.config.ParseValue(config, key, base, None)[0] elif not isinstance(param, dict): raise galsim.GalSimConfigError( "wcs must be either a BaseWCS or a dict") elif 'type' in param: wcs_type = param['type'] else: wcs_type = 'PixelScale' # For these two, just do the usual ParseValue function. if wcs_type in ('Eval', 'Current'): return galsim.config.ParseValue(config, key, base, None)[0] # Check if we can use the current cached object _, orig_index_key = galsim.config.GetIndex(param, base) base['index_key'] = 'image_num' index, _ = galsim.config.GetIndex(param, base) if 'current' in param: cwcs, csafe, cvalue_type, cindex, cindex_key = param['current'] if cindex == index: logger.debug('image %d: The wcs object is already current', base.get('image_num', 0)) return cwcs # Special case: origin == center means to use image_center for the wcs origin if 'origin' in param and param['origin'] == 'center': origin = base['image_center'] param['origin'] = origin if wcs_type not in valid_wcs_types: raise galsim.GalSimConfigValueError("Invalid image.wcs.type.", wcs_type, valid_wcs_types) logger.debug('image %d: Building wcs type %s', base.get('image_num', 0), wcs_type) builder = valid_wcs_types[wcs_type] wcs = builder.buildWCS(param, base, logger) logger.debug('image %d: wcs = %s', base.get('image_num', 0), wcs) param['current'] = wcs, False, None, index, 'image_num' base['index_key'] = orig_index_key return wcs
def _GenerateFromSequence(config, base, value_type): """@brief Return next in a sequence of integers """ ignore = ['default'] opt = { 'first': value_type, 'last': value_type, 'step': value_type, 'repeat': int, 'nitems': int, 'index_key': str } kwargs, safe = GetAllParams(config, base, opt=opt, ignore=ignore) step = kwargs.get('step', 1) first = kwargs.get('first', 0) repeat = kwargs.get('repeat', 1) last = kwargs.get('last', None) nitems = kwargs.get('nitems', None) if repeat <= 0: raise galsim.GalSimConfigValueError( "Invalid repeat for type = Sequence (must be > 0)", repeat) if last is not None and nitems is not None: raise galsim.GalSimConfigError( "At most one of the attributes last and nitems is allowed for type = Sequence" ) index, index_key = galsim.config.GetIndex(kwargs, base, is_sequence=True) #print('in GenFromSequence: index = ',index,index_key) if value_type is bool: # Then there are only really two valid sequences: Either 010101... or 101010... # Aside from the repeat value of course. if first: first = 1 step = -1 nitems = 2 else: first = 0 step = 1 nitems = 2 elif value_type is float: if last is not None: nitems = int((last - first) / step + 0.5) + 1 else: if last is not None: nitems = (last - first) // step + 1 #print('nitems = ',nitems) #print('repeat = ',repeat) index = index // repeat #print('index => ',index) if nitems is not None and nitems > 0: index = index % nitems #print('index => ',index) value = first + index * step #print(base[index_key],'Sequence index = %s + %d*%s = %s'%(first,index,step,value)) return value, False
def ParseValue(config, key, base, value_type): """@brief Read or generate a parameter value from config. @returns the tuple (value, safe). """ param = config[key] #print('ParseValue for key = ',key,', value_type = ',str(value_type)) #print('param = ',param) #print('nums = ',base.get('file_num',0), base.get('image_num',0), base.get('obj_num',0)) if isinstance(param, dict): type_name = param.get('type', None) #print('type = ',type_name) #print(param['type'], value_type) # Check what index key we want to use for this value. index, index_key = galsim.config.GetIndex( param, base, is_sequence=(type_name == 'Sequence')) #print('index, index_key = ',index,index_key) if '_gen_fn' in param: generate_func = param['_gen_fn'] if 'current' in param: cval, csafe, cvalue_type, cindex, cindex_key = param['current'] if 'repeat' in param: repeat = galsim.config.ParseValue(param, 'repeat', base, int)[0] use_current = (cindex // repeat == index // repeat) else: use_current = (cindex == index) if use_current: if (value_type is not None and cvalue_type is not None and cvalue_type != value_type): raise galsim.GalSimConfigError( "Attempt to parse %s multiple times with different value types: " "%s and %s" % (key, value_type, cvalue_type)) #print(index,'Using current value of ',key,' = ',param['current'][0]) return cval, csafe else: # Only need to check this the first time. if 'type' not in param: raise galsim.GalSimConfigError( "%s.type attribute required when providing a dict." % (key)) # Check if the value_type is valid. # (See valid_value_types defined at the top of the file.) if type_name not in valid_value_types: raise galsim.GalSimConfigValueError( "Unrecognized %s.type" % (key), type_name, valid_value_types) # Get the generating function and the list of valid types for it. generate_func, valid_types = valid_value_types[type_name] if value_type not in valid_types: raise galsim.GalSimConfigValueError( "Invalid value_type specified for parameter %s with type=%s." % (key, type_name), value_type, valid_types) param['_gen_fn'] = generate_func #print('generate_func = ',generate_func) val_safe = generate_func(param, base, value_type) #print('returned val, safe = ',val_safe) if isinstance(val_safe, tuple): val, safe = val_safe else: # pragma: no cover # If a user-defined type forgot to return safe, just assume safe = False # It's an easy mistake to make and the TypeError that gets emitted isn't # terribly informative about what the error is. val = val_safe safe = False # Make sure we really got the right type back. (Just in case...) if value_type is not None and not isinstance( val, value_type) and val is not None: val = value_type(val) # Save the current value for possible use by the Current type param['current'] = (val, safe, value_type, index, index_key) #print(key,' = ',val) return val, safe else: # Not a dict # Check for some special markup on string items and convert them to normal dicts. if isinstance(param, basestring): if param[0] == '$': config[key] = {'type': 'Eval', 'str': str(param[1:])} return ParseValue(config, key, base, value_type) if param[0] == '@': config[key] = {'type': 'Current', 'key': str(param[1:])} return ParseValue(config, key, base, value_type) # See if it's already the right kind of object, in which case we can just return it. if value_type is None or isinstance(param, value_type): #print(key,' = ',param) return param, True # Convert lists to dicts with type=List if isinstance(param, list) and value_type is not list: config[key] = {'type': 'List', 'items': param} return ParseValue(config, key, base, value_type) # The rest of these are special processing options for specific value_types: if value_type is galsim.Angle: # Angle is a special case. Angles are specified with a final string to # declare what unit to use. val = _GetAngleValue(param) elif value_type is bool: # For bool, we allow a few special string conversions val = _GetBoolValue(param) elif value_type is galsim.PositionD: # For PositionD, we allow a string of x,y val = _GetPositionValue(param) elif value_type is None or param is None: # If no value_type is given, just return whatever we have in the dict and hope # for the best. val = param else: # If none of the above worked, just try a normal value_type initialization. # This makes sure strings are converted to float (or other type) if necessary. # In particular things like 1.e6 aren't converted to float automatically # by the yaml reader. (Although I think this is a bug.) try: val = value_type(param) except (ValueError, TypeError): raise galsim.GalSimConfigError("Could not parse %s as a %s" % (param, value_type)) #print(key,' = ',val) # Save the converted type for next time so it will hit the first if statement here # instead of recalculating the value. config[key] = val return val, True
def buildImage(self, config, base, image_num, obj_num, logger): """Build an Image containing multiple objects placed at arbitrary locations. Parameters: config: The configuration dict for the image field. base: The base configuration dict. image_num: The current image number. obj_num: The first object number in the image. logger: If given, a logger object to log progress. Returns: the final image and the current noise variance in the image as a tuple """ full_xsize = base['image_xsize'] full_ysize = base['image_ysize'] wcs = base['wcs'] full_image = galsim.ImageF(full_xsize, full_ysize) full_image.setOrigin(base['image_origin']) full_image.wcs = wcs full_image.setZero() base['current_image'] = full_image if 'image_pos' in config and 'world_pos' in config: raise galsim.GalSimConfigValueError( "Both image_pos and world_pos specified for Scattered image.", (config['image_pos'], config['world_pos'])) if 'image_pos' not in config and 'world_pos' not in config: xmin = base['image_origin'].x xmax = xmin + full_xsize-1 ymin = base['image_origin'].y ymax = ymin + full_ysize-1 config['image_pos'] = { 'type' : 'XY' , 'x' : { 'type' : 'Random' , 'min' : xmin , 'max' : xmax }, 'y' : { 'type' : 'Random' , 'min' : ymin , 'max' : ymax } } stamps, current_vars = galsim.config.BuildStamps( self.nobjects, base, logger=logger, obj_num=obj_num, do_noise=False) base['index_key'] = 'image_num' for k in range(self.nobjects): # This is our signal that the object was skipped. if stamps[k] is None: continue bounds = stamps[k].bounds & full_image.bounds logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds)) logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(stamps[k].bounds)) logger.debug('image %d: Overlap = %s',image_num,str(bounds)) if bounds.isDefined(): full_image[bounds] += stamps[k][bounds] else: logger.info( "Object centered at (%d,%d) is entirely off the main image, " "whose bounds are (%d,%d,%d,%d)."%( stamps[k].center.x, stamps[k].center.y, full_image.bounds.xmin, full_image.bounds.xmax, full_image.bounds.ymin, full_image.bounds.ymax)) # Bring the image so far up to a flat noise variance current_var = galsim.config.FlattenNoiseVariance( base, full_image, stamps, current_vars, logger) return full_image, current_var
def BuildGSObject(config, key, base=None, gsparams={}, logger=None): """Build a GSObject from the parameters in config[key]. Parameters: config: A dict with the configuration information. key: The key name in config indicating which object to build. base: The base dict of the configuration. [default: config] gsparams: Optionally, provide non-default GSParams items. Any ``gsparams`` specified at this level will be added to the list. This should be a dict with whatever kwargs should be used in constructing the GSParams object. [default: {}] logger: Optionally, provide a logger for logging debug statements. [default: None] Returns: the tuple (gsobject, safe), where ``gsobject`` is the built object, and ``safe`` is a bool that says whether it is safe to use this object again next time. """ logger = galsim.config.LoggerWrapper(logger) if base is None: base = config logger.debug('obj %d: Start BuildGSObject %s',base.get('obj_num',0),key) # If key isn't in config, then just return None. try: param = config[key] except KeyError: return None, True # Check what index key we want to use for this object. # Note: this call will also set base['index_key'] and base['rng'] to the right values index, index_key = galsim.config.GetIndex(param, base) # Get the type to be parsed. if not 'type' in param: raise galsim.GalSimConfigError("type attribute required in config.%s"%key) type_name = param['type'] # If we are repeating, then we get to use the current object for repeat times. if 'repeat' in param: repeat = galsim.config.ParseValue(param, 'repeat', base, int)[0] else: repeat = 1 # Check if we need to skip this object if 'skip' in param: skip = galsim.config.ParseValue(param, 'skip', base, bool)[0] if skip: logger.debug('obj %d: Skipping because field skip=True',base.get('obj_num',0)) raise SkipThisObject() # Check if we can use the current cached object if 'current' in param: # NB. "current" tuple is (obj, safe, None, index, index_type) cobj, csafe, cvalue_type, cindex, cindex_type = param['current'] if csafe or cindex//repeat == index//repeat: # If logging, explain why we are using the current object. if logger: if csafe: logger.debug('obj %d: current is safe',base.get('obj_num',0)) elif repeat > 1: logger.debug('obj %d: repeat = %d, index = %d, use current object', base.get('obj_num',0),repeat,index) else: logger.debug('obj %d: This object is already current', base.get('obj_num',0)) return cobj, csafe # Set up the initial default list of attributes to ignore while building the object: ignore = [ 'dilate', 'dilation', 'ellip', 'rotate', 'rotation', 'scale_flux', 'magnify', 'magnification', 'shear', 'shift', 'gsparams', 'skip', 'current', 'index_key', 'repeat' ] # There are a few more that are specific to which key we have. if key == 'gal': ignore += [ 'resolution', 'signal_to_noise', 'redshift', 're_from_res' ] elif key == 'psf': ignore += [ 'saved_re' ] else: # As long as key isn't psf, allow resolution. # Ideally, we'd like to check that it's something within the gal hierarchy, but # I don't know an easy way to do that. ignore += [ 'resolution' , 're_from_res' ] # Allow signal_to_noise for PSFs only if there is not also a galaxy. if 'gal' not in base and key == 'psf': ignore += [ 'signal_to_noise'] # If we are specifying the size according to a resolution, then we # need to get the PSF's half_light_radius. if 'resolution' in param: if 'psf' not in base: raise galsim.GalSimConfigError("Cannot use gal.resolution if no psf is set.") if 'saved_re' not in base['psf']: raise galsim.GalSimConfigError( 'Cannot use gal.resolution with psf.type = %s'%base['psf']['type']) psf_re = base['psf']['saved_re'] resolution = galsim.config.ParseValue(param, 'resolution', base, float)[0] gal_re = resolution * psf_re if 're_from_res' not in param: # The first time, check that half_light_radius isn't also specified. if 'half_light_radius' in param: raise galsim.GalSimConfigError( 'Cannot specify both gal.resolution and gal.half_light_radius') param['re_from_res'] = True param['half_light_radius'] = gal_re # Make sure the PSF gets flux=1 unless explicitly overridden by the user. if key == 'psf' and 'flux' not in param and 'signal_to_noise' not in param: param['flux'] = 1 if 'gsparams' in param: gsparams = UpdateGSParams(gsparams, param['gsparams'], base) # See if this type is registered as a valid type. if type_name in valid_gsobject_types: build_func = valid_gsobject_types[type_name] elif type_name in galsim.__dict__: build_func = eval("galsim."+type_name) else: raise galsim.GalSimConfigValueError("Unrecognised gsobject type", type_name) if inspect.isclass(build_func) and issubclass(build_func, galsim.GSObject): gsobject, safe = _BuildSimple(build_func, param, base, ignore, gsparams, logger) else: gsobject, safe = build_func(param, base, ignore, gsparams, logger) # If this is a psf, try to save the half_light_radius in case gal uses resolution. if key == 'psf': try: param['saved_re'] = gsobject.half_light_radius except (AttributeError, NotImplementedError, TypeError): pass # Apply any dilation, ellip, shear, etc. modifications. gsobject, safe1 = TransformObject(gsobject, param, base, logger) safe = safe and safe1 param['current'] = gsobject, safe, None, index, index_key return gsobject, safe
def buildImage(self, config, base, image_num, obj_num, logger): """ Build an Image consisting of a tiled array of postage stamps. @param config The configuration dict for the image field. @param base The base configuration dict. @param image_num The current image number. @param obj_num The first object number in the image. @param logger If given, a logger object to log progress. @returns the final image and the current noise variance in the image as a tuple """ full_xsize = base['image_xsize'] full_ysize = base['image_ysize'] wcs = base['wcs'] full_image = galsim.ImageF(full_xsize, full_ysize) full_image.setOrigin(base['image_origin']) full_image.wcs = wcs full_image.setZero() base['current_image'] = full_image nobjects = self.nx_tiles * self.ny_tiles # Make a list of ix,iy values according to the specified order: if 'order' in config: order = galsim.config.ParseValue(config,'order',base,str)[0].lower() else: order = 'row' if order.startswith('row'): ix_list = [ ix for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ] iy_list = [ iy for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ] elif order.startswith('col'): ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] elif order.startswith('rand'): ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] rng = galsim.config.GetRNG(config, base, logger, 'TiledImage, order = '+order) galsim.random.permute(rng, ix_list, iy_list) else: raise galsim.GalSimConfigValueError("Invalid order.", order, ('row', 'col', 'random')) # Define a 'image_pos' field so the stamps can set their position appropriately in case # we need it for PowerSpectum or NFWHalo. x0 = (self.stamp_xsize-1)/2. + base['image_origin'].x y0 = (self.stamp_ysize-1)/2. + base['image_origin'].y dx = self.stamp_xsize + self.xborder dy = self.stamp_ysize + self.yborder config['image_pos'] = { 'type' : 'XY', 'x' : { 'type' : 'List', 'items' : [ x0 + ix*dx for ix in ix_list ] }, 'y' : { 'type' : 'List', 'items' : [ y0 + iy*dy for iy in iy_list ] } } stamps, current_vars = galsim.config.BuildStamps( nobjects, base, logger=logger, obj_num=obj_num, xsize=self.stamp_xsize, ysize=self.stamp_ysize, do_noise=self.do_noise_in_stamps) base['index_key'] = 'image_num' for k in range(nobjects): logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds)) logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(stamps[k].bounds)) assert full_image.bounds.includes(stamps[k].bounds) b = stamps[k].bounds full_image[b] += stamps[k] # Bring the noise in the image so far up to a flat noise variance # Save the resulting noise variance as self.current_var. current_var = 0 if not self.do_noise_in_stamps: current_var = galsim.config.FlattenNoiseVariance( base, full_image, stamps, current_vars, logger) return full_image, current_var