def _type_by_letter(key): if len(key) < 2: raise galsim.GalSimConfigError("Invalid user-defined variable %r" % key) if key[0] == 'f': return float elif key[0] == 'i': return int elif key[0] == 'b': return bool elif key[0] == 's': return str elif key[0] == 'a': return galsim.Angle elif key[0] == 'p': return galsim.PositionD elif key[0] == 'c': return galsim.CelestialCoord elif key[0] == 'g': return galsim.Shear elif key[0] == 'x': return None else: raise galsim.GalSimConfigError( "Invalid Eval variable: %s (starts with an invalid letter)" % key)
def _BuildConvolve(config, base, ignore, gsparams, logger): """Build a Convolution object. """ req = { 'items' : list } opt = { 'flux' : float } # Only Check, not Get. We need to handle items a bit differently, since it's a list. galsim.config.CheckAllParams(config, req=req, opt=opt, ignore=ignore) gsobjects = [] items = config['items'] if not isinstance(items,list): raise galsim.GalSimConfigError("items entry for type=Convolve is not a list.") safe = True for i in range(len(items)): gsobject, safe1 = BuildGSObject(items, i, base, gsparams, logger) safe = safe and safe1 gsobjects.append(gsobject) if len(gsobjects) == 0: raise galsim.GalSimConfigError("No valid items for type=Convolve") elif len(gsobjects) == 1: gsobject = gsobjects[0] else: if gsparams: gsparams = galsim.GSParams(**gsparams) else: gsparams = None gsobject = galsim.Convolve(gsobjects,gsparams=gsparams) if 'flux' in config: flux, safe1 = galsim.config.ParseValue(config, 'flux', base, float) logger.debug('obj %d: flux == %f',base.get('obj_num',0),flux) gsobject = gsobject.withFlux(flux) safe = safe and safe1 return gsobject, safe
def _GenerateFromNFWHaloMagnification(config, base, value_type): """Return a magnification calculated from an NFWHalo object. """ nfw_halo = galsim.config.GetInputObj('nfw_halo', config, base, 'NFWHaloMagnification') logger = nfw_halo.logger if 'world_pos' not in base: raise galsim.GalSimConfigError("NFWHaloMagnification requested, but no position defined.") pos = base['world_pos'] if 'gal' not in base or 'redshift' not in base['gal']: raise galsim.GalSimConfigError( "NFWHaloMagnification requested, but no gal.redshift defined.") redshift = galsim.config.GetCurrentValue('redshift', base['gal'], float, base) opt = { 'max_mu' : float, 'num' : int } kwargs = galsim.config.GetAllParams(config, base, opt=opt)[0] max_mu = kwargs.get('max_mu', 25.) if not max_mu > 0.: raise galsim.GalSimConfigValueError( "Invalid max_mu for type = NFWHaloMagnification (must be > 0)", max_mu) mu = nfw_halo.getMagnification(pos,redshift) if mu < 0 or mu > max_mu: logger.warning('obj %d: Warning: NFWHalo mu = %f means strong lensing. '%( base['obj_num'],mu) + 'Using mu = %f'%max_mu) mu = max_mu logger.debug('obj %d: NFWHalo mu = %s',base['obj_num'],mu) return mu, False
def _GenerateFromNFWHaloShear(config, base, value_type): """Return a shear calculated from an NFWHalo object. """ nfw_halo = galsim.config.GetInputObj('nfw_halo', config, base, 'NFWHaloShear') logger = nfw_halo.logger if 'world_pos' not in base: raise galsim.GalSimConfigError("NFWHaloShear requested, but no position defined.") pos = base['world_pos'] if 'gal' not in base or 'redshift' not in base['gal']: raise galsim.GalSimConfigError("NFWHaloShear requested, but no gal.redshift defined.") redshift = galsim.config.GetCurrentValue('redshift', base['gal'], float, base) # There aren't any parameters for this, so just make sure num is the only (optional) # one present. galsim.config.CheckAllParams(config, opt={ 'num' : int }) g1,g2 = nfw_halo.getShear(pos,redshift) try: shear = galsim.Shear(g1=g1,g2=g2) except KeyboardInterrupt: raise except Exception as e: logger.warning('obj %d: Warning: NFWHalo shear (g1=%f, g2=%f) is invalid. '%( base['obj_num'],g1,g2) + 'Using shear = 0.') shear = galsim.Shear(g1=0,g2=0) logger.debug('obj %d: NFWHalo shear = %s',base['obj_num'],shear) return shear, False
def _BuildList(config, base, ignore, gsparams, logger): """Build a GSObject selected from a List. """ req = { 'items' : list } opt = { 'index' : float , 'flux' : float } # Only Check, not Get. We need to handle items a bit differently, since it's a list. galsim.config.CheckAllParams(config, req=req, opt=opt, ignore=ignore) items = config['items'] if not isinstance(items,list): raise galsim.GalSimConfigError("items entry for type=List is not a list.") # Setup the indexing sequence if it hasn't been specified using the length of items. galsim.config.SetDefaultIndex(config, len(items)) index, safe = galsim.config.ParseValue(config, 'index', base, int) if index < 0 or index >= len(items): raise galsim.GalSimConfigError("index %d out of bounds for List"%index) gsobject, safe1 = BuildGSObject(items, index, base, gsparams, logger) safe = safe and safe1 if 'flux' in config: flux, safe1 = galsim.config.ParseValue(config, 'flux', base, float) logger.debug('obj %d: flux == %f',base.get('obj_num',0),flux) gsobject = gsobject.withFlux(flux) safe = safe and safe1 return gsobject, safe
def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): """Check that the parameters for a particular item are all valid Parameters: config: The config dict to check req: The required items [default: {}] opt: The optional items [default: {}] single: List of items where exactly one is required [default: []] ignore: Items to ignore [default: []] Returns: a dict, get, with get[key] = value_type for all keys to get. """ if '_get' in config: return config['_get'] get = {} valid_keys = list(req) + list(opt) # Check required items: for (key, value_type) in req.items(): if key in config: get[key] = value_type else: raise galsim.GalSimConfigError( "Attribute %s is required for type = %s" % (key, config.get('type', None))) # Check optional items: for (key, value_type) in opt.items(): if key in config: get[key] = value_type # Check items for which exacly 1 should be defined: for s in single: valid_keys += list(s) count = 0 for (key, value_type) in s.items(): if key in config: count += 1 if count > 1: raise galsim.GalSimConfigError( "Only one of the attributes %s is allowed for type = %s" % (s.keys(), config.get('type', None))) get[key] = value_type if count == 0: raise galsim.GalSimConfigError( "One of the attributes %s is required for type = %s" % (s.keys(), config.get('type', None))) # Check that there aren't any extra keys in config aside from a few we expect: valid_keys += ignore valid_keys += standard_ignore for key in config: # Generators are allowed to use item names that start with _, which we ignore here. if key not in valid_keys and not key.startswith('_'): raise galsim.GalSimConfigError("Unexpected attribute %s found" % (key)) config['_get'] = get return get
def getSNRScale(self, image, config, base, logger): """Calculate the factor by which to rescale the image based on a desired S/N level. Note: The default implementation does this for the gal or psf field, so if a custom stamp builder uses some other way to get the profiles, this method should probably be overridden. Parameters: image: The current image. config: The configuration dict for the stamp field. base: The base configuration dict. logger: If given, a logger object to log progress. Returns: scale_factor """ if 'gal' in base and 'signal_to_noise' in base['gal']: key = 'gal' elif 'gal' not in base and 'psf' in base and 'signal_to_noise' in base[ 'psf']: key = 'psf' else: return 1. if 'flux' in base[key]: raise galsim.GalSimConfigError( 'Only one of signal_to_noise or flux may be specified for %s' % key) if 'image' in base and 'noise' in base['image']: noise_var = galsim.config.CalculateNoiseVariance(base) else: raise galsim.GalSimConfigError( "Need to specify noise level when using %s.signal_to_noise" % key) sn_target = galsim.config.ParseValue(base[key], 'signal_to_noise', base, float)[0] try: # In case noise variance is an image noise_var = noise_var.array.mean() except AttributeError: pass # Now determine what flux we need to get our desired S/N # There are lots of definitions of S/N, but here is the one used by Great08 # We use a weighted integral of the flux: # S = sum W(x,y) I(x,y) / sum W(x,y) # N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2 # Now we assume that Var(I(x,y)) is dominated by the sky noise, so # Var(I(x,y)) = var # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y). # Then a few things cancel and we find that # S/N = sqrt( sum I(x,y)^2 / var ) sn_meas = math.sqrt(np.sum(image.array**2, dtype=float) / noise_var) # Now we rescale the flux to get our desired S/N scale_factor = sn_target / sn_meas return scale_factor
def DrawPSFStamp(psf, config, base, bounds, offset, method, logger): """ Draw an image using the given psf profile. @returns the resulting image. """ if 'draw_method' in config: method = galsim.config.ParseValue(config, 'draw_method', base, str)[0] if method not in valid_draw_methods: raise galsim.GalSimConfigValueError("Invalid draw_method.", method, valid_draw_methods) else: method = 'auto' if 'flux' in config: flux = galsim.config.ParseValue(config, 'flux', base, float)[0] psf = psf.withFlux(flux) if method == 'phot': rng = galsim.config.GetRNG(config, base) n_photons = psf.flux else: rng = None n_photons = 0 wcs = base['wcs'].local(base['image_pos']) im = galsim.ImageF(bounds, wcs=wcs) im = psf.drawImage(image=im, offset=offset, method=method, rng=rng, n_photons=n_photons) if 'signal_to_noise' in config: if 'flux' in config: raise galsim.GalSimConfigError( "Cannot specify both flux and signal_to_noise for psf output") if method == 'phot': raise galsim.GalSimConfigError( "signal_to_noise option not implemented for draw_method = phot" ) if 'image' in base and 'noise' in base['image']: noise_var = galsim.config.CalculateNoiseVariance(base) else: raise galsim.GalSimConfigError( "Need to specify noise level when using psf.signal_to_noise") sn_target = galsim.config.ParseValue(config, 'signal_to_noise', base, float)[0] sn_meas = math.sqrt(np.sum(im.array**2, dtype=float) / noise_var) flux = sn_target / sn_meas im *= flux return im
def reject(self, config, base, prof, psf, image, logger): """Check to see if this object should be rejected. @param config The configuration dict for the stamp field. @param base The base configuration dict. @param prof The profile that was drawn. @param psf The psf that was used to build the profile. @param image The postage stamp image. No noise is on it yet at this point. @param logger If given, a logger object to log progress. @returns whether to reject this object """ # Early exit if no profile if prof is None: return False if 'reject' in config: if galsim.config.ParseValue(config, 'reject', base, bool)[0]: logger.info('obj %d: reject evaluated to True',base['obj_num']) return True if 'min_flux_frac' in config: if not isinstance(prof, galsim.GSObject): raise galsim.GalSimConfigError( "Cannot apply min_flux_frac for stamp types that do not use " "a single GSObject profile.") expected_flux = prof.flux measured_flux = np.sum(image.array, dtype=float) min_flux_frac = galsim.config.ParseValue(config, 'min_flux_frac', base, float)[0] logger.debug('obj %d: flux_frac = %f', base.get('obj_num',0), measured_flux / expected_flux) if measured_flux < min_flux_frac * expected_flux: logger.warning('Object %d: Measured flux = %f < %s * %f.', base['obj_num'], measured_flux, min_flux_frac, expected_flux) return True if 'min_snr' in config or 'max_snr' in config: if not isinstance(prof, galsim.GSObject): raise galsim.GalSimConfigError( "Cannot apply min_snr for stamp types that do not use " "a single GSObject profile.") var = galsim.config.CalculateNoiseVariance(base) sumsq = np.sum(image.array**2, dtype=float) snr = np.sqrt(sumsq / var) logger.debug('obj %d: snr = %f', base.get('obj_num',0), snr) if 'min_snr' in config: min_snr = galsim.config.ParseValue(config, 'min_snr', base, float)[0] if snr < min_snr: logger.warning('Object %d: Measured snr = %f < %s.', base['obj_num'], snr, min_snr) return True if 'max_snr' in config: max_snr = galsim.config.ParseValue(config, 'max_snr', base, float)[0] if snr > max_snr: logger.warning('Object %d: Measured snr = %f > %s.', base['obj_num'], snr, max_snr) return True return False
def buildProfile(self, config, base, psf, gsparams, logger): """ Build the object to be drawn. For the first item in the ring, this is the same as Basic. It stores the galaxy object created on the first time. Then for later stamps in the ring, it retrieves the stored first galaxy and just rotates it before convolving by the psf. @param config The configuration dict for the stamp field. @param base The base configuration dict. @param psf The PSF, if any. This may be None, in which case, no PSF is convolved. @param gsparams A dict of kwargs to use for a GSParams. More may be added to this list by the galaxy object. @param logger If given, a logger object to log progress. @returns the final profile """ # These have all already been checked to exist in SetupRing. num = galsim.config.ParseValue(config, 'num', base, int)[0] index = galsim.config.ParseValue(config, 'index', base, int)[0] if index < 0 or index >= num: raise galsim.GalSimConfigError("index %d out of bounds for Ring" % index) if index % num == 0: # Then we are on the first item in the ring, so make it normally. gal = galsim.config.BuildGSObject(base, 'gal', gsparams=gsparams, logger=logger)[0] if gal is None: raise galsim.GalSimConfigError( "The gal field must define a valid galaxy for stamp type=Ring." ) # Save the galaxy profile for next time. self.first = gal else: # Grab the saved first galaxy. if not hasattr(self, 'first'): raise galsim.GalSimConfigError( "Building Ring after the first item, but no first gal stored." ) gal = self.first full_rot = galsim.config.ParseValue(config, 'full_rotation', base, galsim.Angle)[0] dtheta = full_rot / num gal = gal.rotate(index * dtheta) # Apply any transformations that are given in the stamp field. gal = galsim.config.TransformObject(gal, config, base, logger)[0] if psf is not None: return galsim.Convolve(gal, psf) else: return gal
def _BuildAdd(config, base, ignore, gsparams, logger): """Build a Sum object. """ req = { 'items' : list } opt = { 'flux' : float } # Only Check, not Get. We need to handle items a bit differently, since it's a list. galsim.config.CheckAllParams(config, req=req, opt=opt, ignore=ignore) gsobjects = [] items = config['items'] if not isinstance(items,list): raise galsim.GalSimConfigError("items entry for type=Add is not a list.") safe = True for i in range(len(items)): gsobject, safe1 = BuildGSObject(items, i, base, gsparams, logger) # Skip items with flux=0 if 'flux' in items[i] and galsim.config.GetCurrentValue('flux',items[i],float,base) == 0.: logger.debug('obj %d: Not including component with flux == 0',base.get('obj_num',0)) continue safe = safe and safe1 gsobjects.append(gsobject) if len(gsobjects) == 0: raise galsim.GalSimConfigError("No valid items for type=Add") elif len(gsobjects) == 1: gsobject = gsobjects[0] else: # Special: if the last item in a Sum doesn't specify a flux, we scale it # to bring the total flux up to 1. if ('flux' not in items[-1]) and all('flux' in item for item in items[0:-1]): sum_flux = 0 for item in items[0:-1]: sum_flux += galsim.config.GetCurrentValue('flux',item,float,base) f = 1. - sum_flux if (f < 0): logger.warning( "Warning: Automatic flux for the last item in Sum (to make the total flux=1) " "resulted in negative flux = %f for that item"%f) logger.debug('obj %d: Rescaling final object in sum to have flux = %f', base.get('obj_num',0), f) gsobjects[-1] = gsobjects[-1].withFlux(f) if gsparams: gsparams = galsim.GSParams(**gsparams) else: gsparams = None gsobject = galsim.Add(gsobjects,gsparams=gsparams) if 'flux' in config: flux, safe1 = galsim.config.ParseValue(config, 'flux', base, float) logger.debug('obj %d: flux == %f',base.get('obj_num',0),flux) gsobject = gsobject.withFlux(flux) safe = safe and safe1 return gsobject, safe
def _GenerateFromFormattedStr(config, base, value_type): """@brief Create a string from a format string """ req = {'format': str, 'items': list} # Ignore items for now, we'll deal with it differently. params, safe = GetAllParams(config, base, req=req) format = params['format'] items = params['items'] # Figure out what types we are expecting for the list elements: tokens = format.split('%') val_types = [] skip = False for token in tokens[1:]: # skip first one. # It we have set skip, then skip this one. if skip: skip = False continue # If token == '', then this is a %% in the original string. Skip this and the next token. if len(token) == 0: skip = True continue token = token.lstrip( '0123456789lLh') # ignore field size, and long/short specification if len(token) == 0: raise galsim.GalSimConfigError( "Unable to parse %r as a valid format string" % format) if token[0].lower() in 'diouxX': val_types.append(int) elif token[0].lower() in 'eEfFgG': val_types.append(float) elif token[0].lower() in 'rs': val_types.append(str) else: raise galsim.GalSimConfigError( "Unable to parse %r as a valid format string" % format) if len(val_types) != len(items): raise galsim.GalSimConfigError( "Number of items for FormatStr (%d) does not match number expected from " "format string (%d)" % (len(items), len(val_types))) vals = [] for index in range(len(items)): val, safe1 = ParseValue(items, index, base, val_types[index]) safe = safe and safe1 vals.append(val) final_str = format % tuple(vals) #print(base['obj_num'],'FormattedStr = ',final_str) return final_str, safe
def setup(self, config, base, image_num, obj_num, ignore, logger): """Do the initialization and setup for building the image. This figures out the size that the image will be, but doesn't actually build it yet. @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 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. @param logger If given, a logger object to log progress. @returns xsize, ysize """ logger.debug('image %d: Building Scattered: image, obj = %d,%d', image_num, image_num, obj_num) self.nobjects = self.getNObj(config, base, image_num) logger.debug('image %d: nobj = %d', image_num, self.nobjects) # These are allowed for Scattered, but we don't use them here. extra_ignore = [ 'image_pos', 'world_pos', 'stamp_size', 'stamp_xsize', 'stamp_ysize', 'nobjects' ] opt = {'size': int, 'xsize': int, 'ysize': int} params = galsim.config.GetAllParams(config, base, opt=opt, ignore=ignore + extra_ignore)[0] size = params.get('size', 0) full_xsize = params.get('xsize', size) full_ysize = params.get('ysize', size) if (full_xsize <= 0) or (full_ysize <= 0): raise galsim.GalSimConfigError( "Both image.xsize and image.ysize need to be defined and > 0.") # If image_force_xsize and image_force_ysize were set in config, make sure it matches. if (('image_force_xsize' in base and full_xsize != base['image_force_xsize']) or ('image_force_ysize' in base and full_ysize != base['image_force_ysize'])): raise galsim.GalSimConfigError( "Unable to reconcile required image xsize and ysize with provided " "xsize=%d, ysize=%d, " % (full_xsize, full_ysize)) return full_xsize, full_ysize
def _GenerateFromRandomDistribution(config, base, value_type): """@brief Return a random value drawn from a user-defined probability distribution """ rng = galsim.config.GetRNG(config, base) ignore = [ 'x', 'f', 'x_log', 'f_log' ] opt = {'function' : str, 'interpolant' : str, 'npoints' : int, 'x_min' : float, 'x_max' : float } kwargs, safe = galsim.config.GetAllParams(config, base, opt=opt, ignore=ignore) # Allow the user to give x,f instead of function to define a LookupTable. if 'x' in config or 'f' in config: if 'x' not in config or 'f' not in config: raise galsim.GalSimConfigError( "Both x and f must be provided for type=RandomDistribution") if 'function' in kwargs: raise galsim.GalSimConfigError( "Cannot provide function with x,f for type=RandomDistribution") x = config['x'] f = config['f'] x_log = config.get('x_log', False) f_log = config.get('f_log', False) interpolant = kwargs.pop('interpolant', 'spline') kwargs['function'] = galsim.LookupTable(x=x, f=f, x_log=x_log, f_log=f_log, interpolant=interpolant) else: if 'function' not in kwargs: raise galsim.GalSimConfigError( "function or x,f must be provided for type=RandomDistribution") if 'x_log' in config or 'f_log' in config: raise galsim.GalSimConfigError( "x_log, f_log are invalid with function for type=RandomDistribution") if '_distdev' not in config or config['_distdev_kwargs'] != kwargs: # The overhead for making a DistDeviate is large enough that we'd rather not do it every # time, so first check if we've already made one: distdev=galsim.DistDeviate(rng,**kwargs) config['_distdev'] = distdev config['_distdev_kwargs'] = kwargs else: distdev = config['_distdev'] # Typically, the rng will change between successive calls to this, so reset the # seed. (The other internal calculations don't need to be redone unless the rest of the # kwargs have been changed.) distdev.reset(rng) val = distdev() #print(base['obj_num'],'distdev = ',val) return val, False
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. Parameters: config: The configuration dict for the current item being worked on. base: The base configuration dict. logger: If given, a logger object to log progress. [default: None] 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 buildProfile(self, config, base, psf, gsparams, logger): """Build the surface brightness profile (a GSObject) to be drawn. For the Basic stamp type, this builds a galaxy from the base['gal'] dict and convolves it with the psf (if given). If either the psf or the galaxy is None, then the other one is returned as is. @param config The configuration dict for the stamp field. @param base The base configuration dict. @param psf The PSF, if any. This may be None, in which case, no PSF is convolved. @param gsparams A dict of kwargs to use for a GSParams. More may be added to this list by the galaxy object. @param logger If given, a logger object to log progress. @returns the final profile """ gal = galsim.config.BuildGSObject(base, 'gal', gsparams=gsparams, logger=logger)[0] if psf: if gal: return galsim.Convolve(gal,psf) else: return psf else: if gal: return gal elif 'gal' in base or 'psf' in base: return None else: raise galsim.GalSimConfigError( "At least one of gal or psf must be specified in config. " "If you really don't want any object, use gal type = None.")
def whiten(self, prof, image, config, base, logger): """If appropriate, whiten the resulting image according to the requested noise profile and the amount of noise originally present in the profile. @param prof The profile to draw. @param image The image onto which to draw the profile. @param config The configuration dict for the stamp field. @param base The base configuration dict. @param logger If given, a logger object to log progress. @returns the variance of the resulting whitened (or symmetrized) image. """ # If the object has a noise attribute, then check if we need to do anything with it. current_var = 0. # Default if not overwritten if isinstance(prof,galsim.GSObject) and prof.noise is not None: if 'image' in base and 'noise' in base['image']: noise = base['image']['noise'] whiten = symmetrize = False if 'whiten' in noise: whiten = galsim.config.ParseValue(noise, 'whiten', base, bool)[0] if 'symmetrize' in noise: symmetrize = galsim.config.ParseValue(noise, 'symmetrize', base, int)[0] if whiten and symmetrize: raise galsim.GalSimConfigError('Only one of whiten or symmetrize is allowed') if whiten or symmetrize: # In case the galaxy was cached, update the rng rng = galsim.config.GetRNG(noise, base, logger, "whiten") prof.noise.rng.reset(rng) if whiten: current_var = prof.noise.whitenImage(image) if symmetrize: current_var = prof.noise.symmetrizeImage(image, symmetrize) return current_var
def SetInConfig(config, key, value): """Set the value of a (possibly extended) key in a config dict. If key is a simple string, then this is equivalent to config[key] = value. However, key is allowed to be a chain of keys such as 'gal.items.0.ellip.e', in which case this function will set config['gal']['items'][0]['ellip']['e'] = value. Parameters: config: The configuration dict. key: The possibly extended key. Returns: the value of that key from the config. """ d, k = ParseExtendedKey(config, key) if value == '': # This means remove it, if it is there. d.pop(k, None) else: try: d[k] = value except Exception as e: raise galsim.GalSimConfigError( "Unable to parse extended key %s. Field %s is invalid." % (key, k))
def getNObj(self, config, base, image_num): """Get the number of objects that will be built for this image. Parameters: config: The configuration dict for the image field. base: The base configuration dict. image_num: The current image number. Returns: the number of objects """ orig_index_key = base.get('index_key',None) base['index_key'] = 'image_num' base['image_num'] = image_num # Allow nobjects to be automatic based on input catalog if 'nobjects' not in config: nobj = galsim.config.ProcessInputNObjects(base) if nobj is None: raise galsim.GalSimConfigError( "Attribute nobjects is required for image.type = Scattered") else: nobj = galsim.config.ParseValue(config,'nobjects',base,int)[0] base['index_key'] = orig_index_key return nobj
def GetInputObj(input_type, config, base, param_name): """Get the input object needed for generating a particular value @param input_type The type of input object to get @param config The config dict for this input item @param base The base config dict @param param_name The type of value that we are trying to construct (only used for error messages). """ if '_input_objs' not in base or input_type not in base['_input_objs']: raise galsim.GalSimConfigError("No input %s available for type = %s" % (input_type, param_name)) if 'num' in config: num = galsim.config.ParseValue(config, 'num', base, int)[0] else: num = 0 if num < 0: raise galsim.GalSimConfigValueError( "Invalid num < 0 supplied for %s." % param_name, num) if num >= len(base['_input_objs'][input_type]): raise galsim.GalSimConfigValueError( "Invalid num supplied for %s (too large)" % param_name, num) return base['_input_objs'][input_type][num]
def ParseExtendedKey(config, key): """Traverse all but the last item in an extended key and return the resulting config, key. If key is an extended key like gal.items.0.ellip.e, then this will return the tuple. (config['gal']['items'][0]['ellip'], 'e'). If key is a regular string, then is just returns the original (config, key). @param config The configuration dict. @param key The possibly extended key. @returns the equivalent (config, key) where key is now a regular non-extended key. """ # This is basically identical to the code for Dict.get(key) in catalog.py. chain = key.split('.') while True: d = config k = chain.pop(0) try: k = int(k) except ValueError: pass if len(chain) == 0: break try: config = d[k] except (TypeError, KeyError): # TypeError for the case where d is a float or Position2D, so d[k] is invalid. # KeyError for the case where d is a dict, but k is not a valid key. raise galsim.GalSimConfigError( "Unable to parse extended key %s. Field %s is invalid." % (key, k)) return d, k
def getFilename(self, config, base, logger): """Get the file_name for the current file being worked on. Note that the base class defines a default extension = '.fits'. This can be overridden by subclasses by changing the default_ext property. @param config The configuration dict for the output type. @param base The base configuration dict. @param logger If given, a logger object to log progress. @returns the filename to build. """ if 'file_name' in config: SetDefaultExt(config['file_name'], self.default_ext) file_name = galsim.config.ParseValue(config, 'file_name', base, str)[0] elif 'root' in base and self.default_ext is not None: # If a file_name isn't specified, we use the name of the config file + '.fits' file_name = base['root'] + self.default_ext else: raise galsim.GalSimConfigError( "No file_name specified and unable to generate it automatically." ) # Prepend a dir to the beginning of the filename if requested. if 'dir' in config: dir = galsim.config.ParseValue(config, 'dir', base, str)[0] file_name = os.path.join(dir, file_name) ensure_dir(file_name) return file_name
def CalculateNoiseVariance(config): """ Calculate the noise variance from the noise specified in the noise dict. Parameters: config: The configuration dict Returns: the noise variance """ noise = config['image']['noise'] 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] var = builder.getNoiseVariance(noise, config) config['index_key'] = orig_index_key return var
def addNoise(self, config, base, im, rng, current_var, draw_method, logger): # Build the correlated noise cn = self.getCOSMOSNoise(config, base, rng) var = cn.getVariance() # Subtract off the current variance if any if current_var: logger.debug( 'image %d, obj %d: Target variance is %f, current variance is %f', base.get('image_num', 0), base.get('obj_num', 0), var, current_var) if var < current_var: raise galsim.GalSimConfigError( "Whitening already added more noise than the requested COSMOS noise." ) cn -= galsim.UncorrelatedNoise(current_var, rng=rng, wcs=cn.wcs) # Add the noise to the image im.addNoise(cn) logger.debug( 'image %d, obj %d: Added COSMOS correlated noise with variance = %f', base.get('image_num', 0), base.get('obj_num', 0), var) return var
def addNoise(self, config, base, im, rng, current_var, draw_method, logger): # Read the noise variance var = self.getNoiseVariance(config, base) ret = var # save for the return value # If we already have some variance in the image (from whitening), then we subtract this much # from sigma**2. if current_var: logger.debug( 'image %d, obj %d: Target variance is %f, current variance is %f', base.get('image_num', 0), base.get('obj_num', 0), var, current_var) if var < current_var: raise galsim.GalSimConfigError( "Whitening already added more noise than the requested Gaussian noise." ) var -= current_var # Now apply the noise. import math sigma = math.sqrt(var) im.addNoise(galsim.GaussianNoise(rng, sigma=sigma)) logger.debug('image %d, obj %d: Added Gaussian noise with var = %f', base.get('image_num', 0), base.get('obj_num', 0), var) return ret
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. @param config The configuration dict @param im The image onto which to add the variance values @param 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] @param 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 buildWCS(self, config, base, logger): req = {'items': list} opt = {'index': int} # Only Check, not Get. We need to handle items a bit differently, since it's a list. galsim.config.CheckAllParams(config, req=req, opt=opt) items = config['items'] if not isinstance(items, list): raise galsim.GalSimConfigError( "items entry for type=List is not a list.") # Setup the indexing sequence if it hasn't been specified using the length of items. galsim.config.SetDefaultIndex(config, len(items)) index, safe = galsim.config.ParseValue(config, 'index', base, int) if index < 0 or index >= len(items): raise galsim.GalSimConfigError( "index %d out of bounds for wcs type=List" % index) return BuildWCS(items, index, base)
def test_galsim_config_error(): """Test basic usage of GalSimConfigError """ err = galsim.GalSimConfigError("Test") print('str = ', str(err)) print('repr = ', repr(err)) assert str(err) == "Test" assert isinstance(err, galsim.GalSimError) assert isinstance(err, ValueError) do_pickle(err)
def _GetAngleValue(param): """ @brief Convert a string consisting of a value and an angle unit into an Angle. """ try : value, unit = param.rsplit(None,1) value = float(value) unit = galsim.AngleUnit.from_name(unit) return galsim.Angle(value, unit) except (ValueError, TypeError, AttributeError) as e: raise galsim.GalSimConfigError("Unable to parse %s as an Angle. Caught %s"%(param,e))
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