Coverage for python/lsst/sims/GalSimInterface/galSimCatalogs.py : 26%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This file defines GalSimBase, which is a daughter of InstanceCatalog designed
3to interface with GalSimInterpreter and generate images using GalSim.
5It also defines daughter classes of GalSimBase designed for specific
6classes of astronomical objects:
8GalSimGalaxies
9GalSimAgn
10GalSimStars
11"""
13from builtins import zip
14from builtins import str
15import numpy as np
16import os
18import lsst.utils
19from lsst.sims.utils import arcsecFromRadians
20from lsst.sims.catalogs.definitions import InstanceCatalog
21from lsst.sims.catalogs.decorators import cached
22from lsst.sims.catUtils.mixins import (CameraCoords, AstrometryGalaxies, AstrometryStars,
23 EBVmixin)
24from lsst.sims.GalSimInterface import GalSimInterpreter, GalSimDetector, GalSimCelestialObject
25from lsst.sims.GalSimInterface import GalSimCameraWrapper
26from lsst.sims.GalSimInterface import make_galsim_detector
27from lsst.sims.photUtils import (Sed, Bandpass, BandpassDict,
28 PhotometricParameters)
29from lsst.afw.cameraGeom import DetectorType
31__all__ = ["GalSimGalaxies", "GalSimAgn", "GalSimStars", "GalSimRandomWalk"]
34def _is_null(argument):
35 """
36 Return True if 'argument' is some null value
37 (i.e. 'Null', None, nan).
38 False otherwise.
39 This is used by InstanceCatalog.write_catalog() to identify rows
40 with null values in key columns.
41 """
42 try:
43 str_class = basestring
44 except:
45 str_class = str
47 if argument is None:
48 return True
49 elif isinstance(argument, str_class):
50 if argument.strip().lower() == 'null':
51 return True
52 elif argument.strip().lower() == 'nan':
53 return True
54 elif argument.strip().lower() == 'none':
55 return True
56 elif np.isnan(argument):
57 return True
59 return False
62class GalSimBase(InstanceCatalog, CameraCoords):
63 """
64 The catalog classes in this file use the InstanceCatalog infrastructure to construct
65 FITS images for each detector-filter combination on a simulated camera. This is done by
66 instantiating the class GalSimInterpreter. GalSimInterpreter is the class which
67 actually generates the FITS images. As the GalSim InstanceCatalogs are iterated over,
68 each object in the catalog is passed to the GalSimInterpeter, which adds the object
69 to the appropriate FITS images. The user can then write the images to disk by calling
70 the write_images method in the GalSim InstanceCatalog.
72 Objects are passed to the GalSimInterpreter by the get_fitsFiles getter function, which
73 adds a column to the InstanceCatalog indicating which detectors' FITS files contain each
74 object.
76 Note: because each GalSim InstanceCatalog has its own GalSimInterpreter, it means
77 that each GalSimInterpreter will only draw FITS images containing one type of object
78 (whatever type of object is contained in the GalSim InstanceCatalog). If the user
79 wishes to generate FITS images containing multiple types of object, the method
80 copyGalSimInterpreter allows the user to pass the GalSimInterpreter from one
81 GalSim InstanceCatalog to another (so, the user could create a GalSim Instance
82 Catalog of stars, generate that InstanceCatalog, then create a GalSim InstanceCatalog
83 of galaxies, pass the GalSimInterpreter from the star catalog to this new catalog,
84 and thus create FITS images that contain both stars and galaxies; see galSimCompoundGenerator.py
85 in the examples/ directory of sims_catUtils for an example).
87 This class (GalSimBase) is the base class for all GalSim InstanceCatalogs. Daughter
88 classes of this class need to behave like ordinary InstanceCatalog daughter classes
89 with the following exceptions:
91 1) If they re-define column_outputs, they must be certain to include the column
92 'fitsFiles.' The getter for this column (defined in this class) calls all of the
93 GalSim image generation infrastructure
95 2) Daughter classes of this class must define a member variable galsim_type that is either
96 'sersic' or 'pointSource'. This variable tells the GalSimInterpreter how to draw the
97 object (to allow a different kind of image profile, define a new method in the GalSimInterpreter
98 class similar to drawPoinSource and drawSersic)
100 3) The variables bandpass_names (a list of the form ['u', 'g', 'r', 'i', 'z', 'y']),
101 bandpass_directory, and bandpass_root should be defined to tell the GalSim InstanceCatalog
102 where to find the files defining the bandpasses to be used for these FITS files.
103 The GalSim InstanceCatalog will look for bandpass files in files with the names
105 for bpn in bandpass_names:
106 name = self.bandpass_directory+'/'+self.bandpass_root+'_'+bpn+'.dat'
108 one should also define the following member variables:
110 componentList is a list of files ins banpass_directory containing the response
111 curves for the different components of the camera, e.g.
112 ['detector.dat', 'm1.dat', 'm2.dat', 'm3.dat', 'lens1.dat', 'lens2.dat', 'lens3.dat']
114 atomTransmissionName is the name of the file in bandpass_directory that contains the
115 atmostpheric transmissivity, e.g. 'atmos_std.dat'
117 4) Telescope parameters such as exposure time, area, and gain are stored in the
118 GalSim InstanceCatalog member variable photParams, which is an instantiation of
119 the class PhotometricParameters defined in sims_photUtils.
121 Daughter classes of GalSimBase will generate both FITS images for all of the detectors/filters
122 in their corresponding cameras and InstanceCatalogs listing all of the objects
123 contained in those images. The catalog is written using the normal write_catalog()
124 method provided for all InstanceClasses. The FITS files are drawn using the write_images()
125 method that is unique to GalSim InstanceCatalogs. The FITS file will be named something like:
127 DetectorName_FilterName.fits
129 (a typical LSST fits file might be R_0_0_S_1_0_y.fits)
131 Note: If you call write_images() before iterating over the catalog (either by calling
132 write_catalog() or using the iterator returned by InstanceCatalog.iter_catalog()),
133 you will get empty or incomplete FITS files. Objects are only added to the GalSimInterpreter
134 in the course of iterating over the InstanceCatalog.
135 """
137 seed = 42
139 # This is sort of a hack; it prevents findChipName in coordUtils from dying
140 # if an object lands on multiple science chips.
141 allow_multiple_chips = True
143 # There is no point in writing things to the InstanceCatalog that do not have SEDs and/or
144 # do not land on any detectors
145 cannot_be_null = ['sedFilepath']
147 column_outputs = ['galSimType', 'uniqueId', 'raICRS', 'decICRS',
148 'chipName', 'x_pupil', 'y_pupil', 'sedFilepath',
149 'majorAxis', 'minorAxis', 'sindex', 'halfLightRadius',
150 'npoints', 'positionAngle', 'fitsFiles']
152 transformations = {'raICRS': np.degrees,
153 'decICRS': np.degrees,
154 'x_pupil': arcsecFromRadians,
155 'y_pupil': arcsecFromRadians,
156 'halfLightRadius': arcsecFromRadians}
158 default_formats = {'S': '%s', 'f': '%.9g', 'i': '%i'}
160 # This is used as the delimiter because the names of the detectors printed in the fitsFiles
161 # column contain both ':' and ','
162 delimiter = '; '
164 sedDir = lsst.utils.getPackageDir('sims_sed_library')
166 bandpassNames = None
167 bandpassDir = os.path.join(lsst.utils.getPackageDir('throughputs'), 'baseline')
168 bandpassRoot = 'filter_'
169 componentList = ['detector.dat', 'm1.dat', 'm2.dat', 'm3.dat',
170 'lens1.dat', 'lens2.dat', 'lens3.dat']
171 atmoTransmissionName = 'atmos_std.dat'
173 # allowed_chips is a list of the names of the detectors we actually want to draw.
174 # If 'None', then all chips are drawn.
175 allowed_chips = None
177 # This member variable will define a PSF to convolve with the sources.
178 # See the classes PSFbase and DoubleGaussianPSF in
179 # galSimUtilities.py for more information
180 PSF = None
182 # This member variable can store a GalSim noise model instantiation
183 # which will be applied to the FITS images when they are created
184 noise_and_background = None
186 # Stores the gain and readnoise
187 photParams = PhotometricParameters()
189 # This must be an instantiation of the GalSimCameraWrapper class defined in
190 # galSimCameraWrapper.py
191 _camera_wrapper = None
193 hasBeenInitialized = False
195 galSimInterpreter = None # the GalSimInterpreter instantiation for this catalog
197 totalDrawings = 0
198 totalObjects = 0
200 @property
201 def camera_wrapper(self):
202 return self._camera_wrapper
204 @camera_wrapper.setter
205 def camera_wrapper(self, val):
206 self._camera_wrapper = val
207 self.camera = val.camera
209 def _initializeGalSimCatalog(self):
210 """
211 Initializes an empty list of objects that have already been drawn to FITS images.
212 We do not want to accidentally draw an object twice.
214 Also initializes the GalSimInterpreter by calling self._initializeGalSimInterpreter()
216 Objects are stored based on their uniqueId values.
217 """
218 self.objectHasBeenDrawn = set()
219 self._initializeGalSimInterpreter()
220 self.hasBeenInitialized = True
222 @cached
223 def get_sedFilepath(self):
224 """
225 Maps the name of the SED as stored in the database to the file stored in
226 sims_sed_library
227 """
228 # copied from the phoSim catalogs
229 return np.array([self.specFileMap[k] if k in self.specFileMap else None
230 for k in self.column_by_name('sedFilename')])
232 def _calcSingleGalSimSed(self, sedName, zz, iAv, iRv, gAv, gRv, norm):
233 """
234 correct the SED for redshift, dust, etc. Return an Sed object as defined in
235 sims_photUtils/../../Sed.py
236 """
237 if _is_null(sedName):
238 return None
239 sed = Sed()
240 sed.readSED_flambda(os.path.join(self.sedDir, sedName))
241 imsimband = Bandpass()
242 imsimband.imsimBandpass()
243 # normalize the SED
244 # Consulting the file sed.py in GalSim/galsim/ it appears that GalSim expects
245 # its SEDs to ultimately be in units of ergs/nm so that, when called, they can
246 # be converted to photons/nm (see the function __call__() and the assignment of
247 # self._rest_photons in the __init__() of galsim's sed.py file). Thus, we need
248 # to read in our SEDs, normalize them, and then multiply by the exposure time
249 # and the effective area to get from ergs/s/cm^2/nm to ergs/nm.
250 #
251 # The gain parameter should convert between photons and ADU (so: it is the
252 # traditional definition of "gain" -- electrons per ADU -- multiplied by the
253 # quantum efficiency of the detector). Because we fold the quantum efficiency
254 # of the detector into our total_[u,g,r,i,z,y].dat bandpass files
255 # (see the readme in the THROUGHPUTS_DIR/baseline/), we only need to multiply
256 # by the electrons per ADU gain.
257 #
258 # We will take these parameters from an instantiation of the PhotometricParameters
259 # class (which can be reassigned by defining a daughter class of this class)
260 #
261 fNorm = sed.calcFluxNorm(norm, imsimband)
262 sed.multiplyFluxNorm(fNorm)
264 # apply dust extinction (internal)
265 if iAv != 0.0 and iRv != 0.0:
266 a_int, b_int = sed.setupCCM_ab()
267 sed.addDust(a_int, b_int, A_v=iAv, R_v=iRv)
269 # 22 June 2015
270 # apply redshift; there is no need to apply the distance modulus from
271 # sims/photUtils/CosmologyWrapper; magNorm takes that into account
272 # however, magNorm does not take into account cosmological dimming
273 if zz != 0.0:
274 sed.redshiftSED(zz, dimming=True)
276 # apply dust extinction (galactic)
277 if gAv != 0.0 and gRv != 0.0:
278 a_int, b_int = sed.setupCCM_ab()
279 sed.addDust(a_int, b_int, A_v=gAv, R_v=gRv)
280 return sed
282 def _calculateGalSimSeds(self):
283 """
284 Apply any physical corrections to the objects' SEDS (redshift them, apply dust, etc.).
286 Return a generator that serves up the Sed objects in order.
287 """
288 actualSEDnames = self.column_by_name('sedFilepath')
289 redshift = self.column_by_name('redshift')
290 internalAv = self.column_by_name('internalAv')
291 internalRv = self.column_by_name('internalRv')
292 galacticAv = self.column_by_name('galacticAv')
293 galacticRv = self.column_by_name('galacticRv')
294 magNorm = self.column_by_name('magNorm')
296 return (self._calcSingleGalSimSed(*args) for args in
297 zip(actualSEDnames, redshift, internalAv, internalRv,
298 galacticAv, galacticRv, magNorm))
300 @cached
301 def get_fitsFiles(self, checkpoint_file=None, nobj_checkpoint=1000):
302 """
303 This getter returns a column listing the names of the detectors whose corresponding
304 FITS files contain the object in question. The detector names will be separated by a '//'
306 This getter also passes objects to the GalSimInterpreter to actually draw the FITS
307 images.
309 WARNING: do not include 'fitsFiles' in the cannot_be_null list of non-null columns.
310 If you do that, this method will be called several times by the catalog, as it
311 attempts to determine which rows are actually in the catalog. That will cause
312 your images to have too much flux in them.
313 """
314 if self.bandpassNames is None:
315 if isinstance(self.obs_metadata.bandpass, list):
316 self.bandpassNames = [self.obs_metadata.bandpass]
317 else:
318 self.bandpassNames = self.obs_metadata.bandpass
320 objectNames = self.column_by_name('uniqueId')
321 xPupil = self.column_by_name('x_pupil')
322 yPupil = self.column_by_name('y_pupil')
323 halfLight = self.column_by_name('halfLightRadius')
324 minorAxis = self.column_by_name('minorAxis')
325 majorAxis = self.column_by_name('majorAxis')
326 positionAngle = self.column_by_name('positionAngle')
327 sindex = self.column_by_name('sindex')
328 npoints = self.column_by_name('npoints')
329 gamma1 = self.column_by_name('gamma1')
330 gamma2 = self.column_by_name('gamma2')
331 kappa = self.column_by_name('kappa')
333 sedList = self._calculateGalSimSeds()
335 if self.hasBeenInitialized is False and len(objectNames) > 0:
336 # This needs to be here in case, instead of writing the whole catalog with write_catalog(),
337 # the user wishes to iterate through the catalog with InstanceCatalog.iter_catalog(),
338 # which will not call write_header()
339 self._initializeGalSimCatalog()
340 if not hasattr(self, 'bandpassDict'):
341 raise RuntimeError('ran initializeGalSimCatalog but do not have bandpassDict')
342 self.galSimInterpreter.checkpoint_file = checkpoint_file
343 self.galSimInterpreter.nobj_checkpoint = nobj_checkpoint
344 self.galSimInterpreter.restore_checkpoint(self._camera_wrapper,
345 self.photParams,
346 self.obs_metadata,
347 epoch=self.db_obj.epoch)
349 output = []
350 for (name, xp, yp, hlr, minor, major, pa, ss, sn, npo, gam1, gam2, kap) in \
351 zip(objectNames, xPupil, yPupil, halfLight,
352 minorAxis, majorAxis, positionAngle, sedList, sindex, npoints,
353 gamma1, gamma2, kappa):
355 if name in self.objectHasBeenDrawn:
356 raise RuntimeError('Trying to draw %s more than once ' % str(name))
357 elif ss is None:
358 raise RuntimeError('Trying to draw an object with SED == None')
359 else:
361 self.objectHasBeenDrawn.add(name)
363 if name not in self.galSimInterpreter.drawn_objects:
365 gsObj = GalSimCelestialObject(self.galsim_type, xp, yp,
366 hlr, minor, major, pa, sn,
367 ss, self.bandpassDict, self.photParams,
368 npo, None, None, None,
369 gam1, gam2, kap, uniqueId=name)
371 # actually draw the object
372 detectorsString = self.galSimInterpreter.drawObject(gsObj)
373 else:
374 # For objects that have already been drawn in the
375 # checkpointed data, use a blank string.
376 detectorsString = ''
378 output.append(detectorsString)
380 # Force checkpoint at the end (if a checkpoint file has been specified).
381 if self.galSimInterpreter is not None:
382 self.galSimInterpreter.write_checkpoint(force=True)
383 return np.array(output)
385 def setPSF(self, PSF):
386 """
387 Set the PSF of this GalSimCatalog after instantiation.
389 @param [in] PSF is an instantiation of a GalSimPSF class.
390 """
391 self.PSF = PSF
392 if self.galSimInterpreter is not None:
393 self.galSimInterpreter.setPSF(PSF=PSF)
395 def copyGalSimInterpreter(self, otherCatalog):
396 """
397 Copy the camera, GalSimInterpreter, from another GalSim InstanceCatalog
398 so that multiple types of object (stars, AGN, galaxy bulges, galaxy disks, etc.)
399 can be drawn on the same FITS files.
401 @param [in] otherCatalog is another GalSim InstanceCatalog that already has
402 an initialized GalSimInterpreter
404 See galSimCompoundGenerator.py in the examples/ directory of sims_catUtils for
405 an example of how this is used.
406 """
407 self.camera_wrapper = otherCatalog.camera_wrapper
408 self.photParams = otherCatalog.photParams
409 self.PSF = otherCatalog.PSF
410 self.noise_and_background = otherCatalog.noise_and_background
411 if otherCatalog.hasBeenInitialized:
412 self.bandpassDict = otherCatalog.bandpassDict
413 self.galSimInterpreter = otherCatalog.galSimInterpreter
415 def _initializeGalSimInterpreter(self):
416 """
417 This method creates the GalSimInterpreter (if it is None)
419 This method reads in all of the data about the camera and pass it into
420 the GalSimInterpreter.
422 This method calls _getBandpasses to construct the paths to
423 the files containing the bandpass data.
424 """
426 if not isinstance(self.camera_wrapper, GalSimCameraWrapper):
427 raise RuntimeError("GalSimCatalog.camera_wrapper must be an instantiation of "
428 "GalSimCameraWrapper or one of its daughter classes\n"
429 "It is actually of type %s" % str(type(self.camera_wrapper)))
431 if self.galSimInterpreter is None:
433 # This list will contain instantiations of the GalSimDetector class
434 # (see galSimInterpreter.py), which stores detector information in a way
435 # that the GalSimInterpreter will understand
436 detectors = []
438 for dd in self.camera_wrapper.camera:
439 if dd.getType() == DetectorType.WAVEFRONT or dd.getType() == DetectorType.GUIDER:
440 # This package does not yet handle the 90-degree rotation
441 # in WCS that occurs for wavefront or guide sensors
442 continue
444 if self.allowed_chips is None or dd.getName() in self.allowed_chips:
445 detectors.append(make_galsim_detector(self.camera_wrapper, dd.getName(),
446 self.photParams, self.obs_metadata,
447 epoch=self.db_obj.epoch))
449 if not hasattr(self, 'bandpassDict'):
450 if self.noise_and_background is not None:
451 if self.obs_metadata.m5 is None:
452 raise RuntimeError('WARNING in GalSimCatalog; you did not specify m5 in your '
453 'obs_metadata. m5 is required in order to '
454 'add noise to your images')
456 for name in self.bandpassNames:
457 if name not in self.obs_metadata.m5:
458 raise RuntimeError('WARNING in GalSimCatalog; your obs_metadata does not have ' +
459 'm5 values for all of your bandpasses \n' +
460 'bandpass has: %s \n' % self.bandpassNames.__repr__() +
461 'm5 has: %s ' % list(self.obs_metadata.m5.keys()).__repr__())
463 if self.obs_metadata.seeing is None:
464 raise RuntimeError('WARNING in GalSimCatalog; you did not specify seeing in your '
465 'obs_metadata. seeing is required in order to add '
466 'noise to your images')
468 for name in self.bandpassNames:
469 if name not in self.obs_metadata.seeing:
470 raise RuntimeError('WARNING in GalSimCatalog; your obs_metadata does not have ' +
471 'seeing values for all of your bandpasses \n' +
472 'bandpass has: %s \n' % self.bandpassNames.__repr__() +
473 'seeing has: %s ' % list(self.obs_metadata.seeing.keys()).__repr__())
475 (self.bandpassDict,
476 hardwareDict) = BandpassDict.loadBandpassesFromFiles(bandpassNames=self.bandpassNames,
477 filedir=self.bandpassDir,
478 bandpassRoot=self.bandpassRoot,
479 componentList=self.componentList,
480 atmoTransmission=os.path.join(self.bandpassDir,
481 self.atmoTransmissionName))
483 self.galSimInterpreter = GalSimInterpreter(obs_metadata=self.obs_metadata,
484 epoch=self.db_obj.epoch,
485 detectors=detectors,
486 bandpassDict=self.bandpassDict,
487 noiseWrapper=self.noise_and_background,
488 seed=self.seed)
490 self.galSimInterpreter.setPSF(PSF=self.PSF)
494 def write_images(self, nameRoot=None):
495 """
496 Writes the FITS images associated with this InstanceCatalog.
498 Cannot be called before write_catalog is called.
500 @param [in] nameRoot is an optional string prepended to the names
501 of the FITS images. The FITS images will be named
503 @param [out] namesWritten is a list of the names of the FITS files generated
505 nameRoot_DetectorName_FilterName.fits
507 (e.g. myImages_R_0_0_S_1_1_y.fits for an LSST-like camera with
508 nameRoot = 'myImages')
509 """
510 namesWritten = self.galSimInterpreter.writeImages(nameRoot=nameRoot)
512 return namesWritten
515class GalSimGalaxies(GalSimBase, AstrometryGalaxies, EBVmixin):
516 """
517 This is a GalSimCatalog class for galaxy components (i.e. objects that are shaped
518 like Sersic profiles).
520 See the docstring in GalSimBase for explanation of how this class should be used.
521 """
523 catalog_type = 'galsim_galaxy'
524 galsim_type = 'sersic'
525 default_columns = [('galacticAv', 0.1, float),
526 ('galacticRv', 3.1, float),
527 ('galSimType', 'sersic', str, 6),
528 ('npoints', 0, int),
529 ('gamma1', 0.0, float),
530 ('gamma2', 0.0, float),
531 ('kappa', 0.0, float)]
533class GalSimRandomWalk(GalSimBase, AstrometryGalaxies, EBVmixin):
534 """
535 This is a GalSimCatalog class for galaxy components (i.e. objects that are shaped
536 like Sersic profiles).
538 See the docstring in GalSimBase for explanation of how this class should be used.
539 """
541 catalog_type = 'galsim_random_walk'
542 galsim_type = 'RandomWalk'
543 default_columns = [('galacticAv', 0.1, float),
544 ('galacticRv', 3.1, float),
545 ('galSimType', 'RandomWalk', str, 10),
546 ('sindex', 0.0, float),
547 ('gamma1', 0.0, float),
548 ('gamma2', 0.0, float),
549 ('kappa', 0.0, float)]
551class GalSimAgn(GalSimBase, AstrometryGalaxies, EBVmixin):
552 """
553 This is a GalSimCatalog class for AGN.
555 See the docstring in GalSimBase for explanation of how this class should be used.
556 """
557 catalog_type = 'galsim_agn'
558 galsim_type = 'pointSource'
559 default_columns = [('galacticAv', 0.1, float),
560 ('galacticRv', 3.1, float),
561 ('galSimType', 'pointSource', str, 11),
562 ('majorAxis', 0.0, float),
563 ('minorAxis', 0.0, float),
564 ('sindex', 0.0, float),
565 ('npoints', 0, int),
566 ('positionAngle', 0.0, float),
567 ('halfLightRadius', 0.0, float),
568 ('internalAv', 0.0, float),
569 ('internalRv', 0.0, float),
570 ('gamma1', 0.0, float),
571 ('gamma2', 0.0, float),
572 ('kappa', 0.0, float)]
575class GalSimStars(GalSimBase, AstrometryStars):
576 """
577 This is a GalSimCatalog class for stars.
579 See the docstring in GalSimBase for explanation of how this class should be used.
580 """
581 catalog_type = 'galsim_stars'
582 galsim_type = 'pointSource'
583 default_columns = [('galacticAv', 0.1, float),
584 ('galacticRv', 3.1, float),
585 ('galSimType', 'pointSource', str, 11),
586 ('internalAv', 0.0, float),
587 ('internalRv', 0.0, float),
588 ('redshift', 0.0, float),
589 ('majorAxis', 0.0, float),
590 ('minorAxis', 0.0, float),
591 ('sindex', 0.0, float),
592 ('npoints', 0, int),
593 ('positionAngle', 0.0, float),
594 ('halfLightRadius', 0.0, float),
595 ('gamma1', 0.0, float),
596 ('gamma2', 0.0, float),
597 ('kappa', 0.0, float)]