From a50720bb542a40e5e6dfd6c166a7c458a873b018 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 00:12:16 -0400 Subject: [PATCH 01/96] Convert RuntimeErrors raised by GalSim into GalSimErrors (#755) --- galsim/__init__.py | 1 + galsim/__main__.py | 1 + galsim/bounds.py | 1 + galsim/chromatic.py | 21 +++++----- galsim/config/extra.py | 4 +- galsim/config/extra_truth.py | 2 +- galsim/config/noise.py | 8 ++-- galsim/config/output.py | 2 +- galsim/config/stamp.py | 2 +- galsim/config/stamp_ring.py | 3 +- galsim/correlatednoise.py | 6 ++- galsim/download_cosmos.py | 1 - galsim/errors.py | 25 ++++++++++++ galsim/fds_test.py | 2 +- galsim/fft.py | 1 + galsim/fits.py | 10 +++-- galsim/fitswcs.py | 30 +++++++------- galsim/gsobject.py | 7 ++-- galsim/hsm.py | 21 ++++++---- galsim/image.py | 18 +++++---- galsim/integ.py | 6 ++- galsim/interpolant.py | 1 + galsim/interpolatedimage.py | 3 +- galsim/lensing_ps.py | 14 ++++--- galsim/nfw_halo.py | 1 + galsim/noise.py | 14 ++++--- galsim/phase_psf.py | 3 +- galsim/phase_screens.py | 2 +- galsim/photon_array.py | 4 +- galsim/pse.py | 4 +- galsim/random.py | 1 + galsim/real.py | 8 ++-- galsim/scene.py | 9 +++-- galsim/sed.py | 4 +- galsim/transform.py | 1 + galsim/utilities.py | 5 ++- galsim/wcs.py | 4 +- galsim/wfirst/wfirst_backgrounds.py | 4 +- galsim/wfirst/wfirst_psfs.py | 2 +- galsim/wfirst/wfirst_wcs.py | 6 +-- include/galsim/hsm/PSFCorr.h | 2 +- tests/test_chromatic.py | 6 +-- tests/test_config_gsobject.py | 2 +- tests/test_config_image.py | 10 ++--- tests/test_config_noise.py | 8 ++-- tests/test_hsm.py | 26 ++++++------ tests/test_image.py | 62 ++++++++++++++--------------- tests/test_integ.py | 2 +- tests/test_lensing.py | 2 +- tests/test_noise.py | 12 +++--- tests/test_sed.py | 2 +- 51 files changed, 231 insertions(+), 165 deletions(-) create mode 100644 galsim/errors.py diff --git a/galsim/__init__.py b/galsim/__init__.py index 84af8749cf7..e4b299e4cde 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -100,6 +100,7 @@ from .catalog import Catalog, Dict, OutputCatalog from .scene import COSMOSCatalog from .table import LookupTable, LookupTable2D +from .errors import GalSimError # Image from .image import Image, ImageS, ImageI, ImageF, ImageD, ImageCF, ImageCD, ImageUS, ImageUI, _Image diff --git a/galsim/__main__.py b/galsim/__main__.py index 9160b25fa02..85b8d1361fa 100644 --- a/galsim/__main__.py +++ b/galsim/__main__.py @@ -17,4 +17,5 @@ # from .main import main + main() diff --git a/galsim/bounds.py b/galsim/bounds.py index 0c3c3e49592..44a6e5060c2 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -20,6 +20,7 @@ """ import math + from . import _galsim from .position import Position, PositionI, PositionD diff --git a/galsim/chromatic.py b/galsim/chromatic.py index d11cba5f970..24688a06c53 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,6 +35,7 @@ from .utilities import lazy_property from . import utilities from . import integ +from .errors import GalSimError class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -588,8 +589,8 @@ def withMagnitude(self, target_magnitude, bandpass): @returns the new normalized ChromaticObject. """ if bandpass.zeropoint is None: - raise RuntimeError("Cannot call ChromaticObject.withMagnitude on this bandpass, because" - " it does not have a zeropoint. See Bandpass.withZeropoint()") + raise GalSimError("Cannot call ChromaticObject.withMagnitude on this bandpass, because" + " it does not have a zeropoint. See Bandpass.withZeropoint()") current_magnitude = self.calculateMagnitude(bandpass) norm = 10**(-0.4*(target_magnitude - current_magnitude)) return self * norm @@ -1095,8 +1096,8 @@ def _imageAtWavelength(self, wave): """ # First, some wavelength-related sanity checks. if wave < np.min(self.waves) or wave > np.max(self.waves): - raise RuntimeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(wave, np.min(self.waves), np.max(self.waves))) + raise GalSimError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(wave, np.min(self.waves), np.max(self.waves))) # Figure out where the supplied wavelength is compared to the list of wavelengths on which # images were originally tabulated. @@ -1163,13 +1164,13 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', wave_list, _, _ = utilities.combine_wave_list(wave_objs) if np.min(wave_list) < np.min(self.waves): - raise RuntimeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.min(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(np.min(wave_list), np.min(self.waves), + np.max(self.waves))) if np.max(wave_list) > np.max(self.waves): - raise RuntimeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.max(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(np.max(wave_list), np.min(self.waves), + np.max(self.waves))) # The integration is carried out using the following two basic principles: # (1) We use linear interpolation between the stored images to get an image at a given diff --git a/galsim/config/extra.py b/galsim/config/extra.py index c250ec971c5..d25669a4b83 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -480,9 +480,9 @@ def writeHdu(self, config, base, logger): """ n = len(self.data) if n == 0: - raise RuntimeError("No %s images were created."%self._extra_output_key) + raise galsim.GalSimError("No %s images were created."%self._extra_output_key) elif n > 1: - raise RuntimeError( + raise galsim.GalSimError( "%d %s images were created, but expecting only 1."%(n,self._extra_output_key)) return self.data[0] diff --git a/galsim/config/extra_truth.py b/galsim/config/extra_truth.py index 9eb642ab895..74a5195e1dc 100644 --- a/galsim/config/extra_truth.py +++ b/galsim/config/extra_truth.py @@ -86,7 +86,7 @@ def processStamp(self, obj_num, config, base, logger): base['obj_num']) logger.error("Types for current object = %s",repr(types)) logger.error("Expecting types = %s",repr(self.scratch['types'])) - raise RuntimeError("Type mismatch found when building truth catalog.") + raise galsim.GalSimError("Type mismatch found when building truth catalog.") self.scratch[obj_num] = row # The function to call at the end of building each file to finalize the truth catalog diff --git a/galsim/config/noise.py b/galsim/config/noise.py index 8976f3c34a1..829185432a0 100644 --- a/galsim/config/noise.py +++ b/galsim/config/noise.py @@ -268,7 +268,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): 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 RuntimeError( + raise galsim.GalSimError( "Whitening already added more noise than the requested Gaussian noise.") var -= current_var @@ -324,7 +324,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): else: test = (total_sky < current_var) if test: - raise RuntimeError( + raise galsim.GalSimError( "Whitening already added more noise than the requested Poisson noise.") total_sky -= current_var extra_sky -= current_var @@ -428,7 +428,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): target_var, current_var) test = target_var < current_var if test: - raise RuntimeError( + raise galsim.GalSimError( "Whitening already added more noise than the requested CCD noise.") if read_noise_var_adu >= current_var: # First try to take away from the read_noise, since this one is actually Gaussian. @@ -540,7 +540,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): 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 RuntimeError( + raise galsim.GalSimError( "Whitening already added more noise than the requested COSMOS noise.") cn -= galsim.UncorrelatedNoise(current_var, rng=rng, wcs=cn.wcs) diff --git a/galsim/config/output.py b/galsim/config/output.py index e2fad2963f3..72a0d647646 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -117,7 +117,7 @@ def done_func(logger, proc, k, result, t2): file_num, file_name = info[k] file_name2, t = result # This is the t for which 0 means the file was skipped. if file_name2 != file_name: - raise RuntimeError("Files seem to be out of sync. %s != %s",file_name, file_name2) + raise galsim.GalSimError("Files seem to be out of sync. %s != %s",file_name, file_name2) if t != 0 and logger: if proc is None: s0 = '' else: s0 = '%s: '%proc diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 5f3725eb2b0..cfeb2aedee2 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -403,7 +403,7 @@ def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): builder.reset(config, logger) continue else: - raise RuntimeError( + raise galsim.GalSimError( "Rejected an object %d times. If this is expected, "%ntries+ "you should specify a larger stamp.retry_failures.") diff --git a/galsim/config/stamp_ring.py b/galsim/config/stamp_ring.py index 121af198aa6..2ca5cfb9350 100644 --- a/galsim/config/stamp_ring.py +++ b/galsim/config/stamp_ring.py @@ -103,7 +103,8 @@ def buildProfile(self, config, base, psf, gsparams, logger): else: # Grab the saved first galaxy. if not hasattr(self, 'first'): - raise RuntimeError("Building Ring after the first item, but no first gal stored.") + raise galsim.GalSimError( + "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 diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 63a900b3adc..13073843c74 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -21,10 +21,12 @@ import numpy as np from future.utils import iteritems + from .image import Image from .random import BaseDeviate from .gsobject import GSObject from . import utilities +from .errors import GalSimError def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -240,7 +242,7 @@ def applyTo(self, image): return image def applyToView(self, image_view): - raise RuntimeError( + raise GalSimError( "CorrelatedNoise can only be applied to a regular Image, not an ImageView") def whitenImage(self, image): @@ -724,7 +726,7 @@ def _get_update_rootps(self, shape, wcs): self._variance_stored = var if var <= 0.: - raise RuntimeError("CorrelatedNoise found to have negative variance.") + raise GalSimError("CorrelatedNoise found to have negative variance.") # Then calculate the sqrt(PS) that will be used to generate the actual noise. First do # the power spectrum (PS) diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 8499a913ae1..9c780e9ed1e 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -21,7 +21,6 @@ from __future__ import print_function from builtins import input - import os, sys, tarfile, subprocess, shutil, json try: from urllib2 import urlopen diff --git a/galsim/errors.py b/galsim/errors.py new file mode 100644 index 00000000000..82ef54b7f59 --- /dev/null +++ b/galsim/errors.py @@ -0,0 +1,25 @@ +# Copyright (c) 2012-2018 by the GalSim developers team on GitHub +# https://github.com/GalSim-developers +# +# This file is part of GalSim: The modular galaxy image simulation toolkit. +# https://github.com/GalSim-developers/GalSim +# +# GalSim is free software: redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions, and the disclaimer given in the accompanying LICENSE +# file. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the disclaimer given in the documentation +# and/or other materials provided with the distribution. +# + +# Define the class hierarchy for errors and warnings emitted by GalSim that aren't +# obviously one of the standard python errors. + +# Base class for all GalSim-emitted run-time errors. +class GalSimError(RuntimeError): + pass + diff --git a/galsim/fds_test.py b/galsim/fds_test.py index dde0355f9b5..cb6b5095dd6 100644 --- a/galsim/fds_test.py +++ b/galsim/fds_test.py @@ -40,8 +40,8 @@ """ from __future__ import print_function - import builtins + openfiles = set() oldfile = builtins.file class newfile(oldfile): diff --git a/galsim/fft.py b/galsim/fft.py index 93e76223a84..9c276c2d223 100644 --- a/galsim/fft.py +++ b/galsim/fft.py @@ -35,6 +35,7 @@ """ import numpy as np + from . import _galsim from .image import Image, ImageD, ImageCD from .bounds import BoundsI diff --git a/galsim/fits.py b/galsim/fits.py index 44a6333fed9..13d55cc453c 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -26,7 +26,9 @@ from past.builtins import basestring import os import numpy as np + from .image import Image +from .errors import GalSimError ############################################################################################## @@ -216,7 +218,7 @@ def __call__(self, file, dir, file_compress): except: # pragma: no cover self.gz_index += 1 self.gz = self.gz_methods[self.gz_index] - raise RuntimeError("None of the options for gunzipping were successful.") + raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': with open(file) as fid: pass while self.bz2_index < len(self.bz2_methods): @@ -227,7 +229,7 @@ def __call__(self, file, dir, file_compress): except: # pragma: no cover self.bz2_index += 1 self.bz2 = self.bz2_methods[self.bz2_index] - raise RuntimeError("None of the options for bunzipping were successful.") + raise GalSimError("None of the options for bunzipping were successful.") else: raise ValueError("Unknown file_compression") _read_file = _ReadFile() @@ -393,7 +395,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) except: # pragma: no cover self.gz_index += 1 self.gz = self.gz_methods[self.gz_index] - raise RuntimeError("None of the options for gunzipping were successful.") + raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': while self.bz2_index < len(self.bz2_methods): try: @@ -403,7 +405,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) except: # pragma: no cover self.bz2_index += 1 self.bz2 = self.bz2_methods[self.bz2_index] - raise RuntimeError("None of the options for bunzipping were successful.") + raise GalSimError("None of the options for bunzipping were successful.") else: raise ValueError("Unknown file_compression") diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 9beb584b93d..809650eadf7 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -23,11 +23,13 @@ import warnings import numpy as np + from .wcs import CelestialWCS, JacobianWCS, AffineTransform from .position import PositionD, PositionI from .angle import radians, arcsec, degrees, AngleUnit from . import _galsim from . import fits +from .errors import GalSimError ######################################################################################### # @@ -137,7 +139,7 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= # TODO: If they ever fix this bug, use the correct version here. if (astropy.__version__ < '999' and header is not None and 'CTYPE1' in header and 'ZPX' in header['CTYPE1'].upper()): - raise RuntimeError("AstropyWCS cannot (always) parse ZPX files") + raise GalSimError("AstropyWCS cannot (always) parse ZPX files") # Load the wcs from the header. if header is not None: @@ -162,7 +164,7 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= warnings.simplefilter("ignore") ra, dec = wcs.all_pix2world( [ [0, 0] ], 1)[0] except Exception as err: # pragma: no cover - raise RuntimeError("AstropyWCS was unable to read the WCS specification in the header.") + raise GalSimError("AstropyWCS was unable to read the WCS specification in the header.") self._wcs = wcs @@ -452,7 +454,7 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= # We can only handle WCS with 2 pixel axes (given by Nin) and 2 WCS axes # (given by Nout). if wcsinfo.Nin != 2 or wcsinfo.Nout != 2: - raise RuntimeError("The world coordinate system is not 2-dimensional") + raise GalSimError("The world coordinate system is not 2-dimensional") if file_name is not None: fits.closeHDUList(hdu_list, fin) @@ -480,7 +482,7 @@ def _load_from_header(self, header, hdu): wcsinfo = fc.read() if wcsinfo is None: - raise RuntimeError("Failed to read WCS information from fits file") + raise GalSimError("Failed to read WCS information from fits file") # The PyAst WCS might not have (RA,Dec) axes, which we want. It might for instance have # (Dec, RA) instead. If it's possible to convert to an (RA,Dec) system, this next line @@ -488,7 +490,7 @@ def _load_from_header(self, header, hdu): # cf. https://github.com/timj/starlink-pyast/issues/8 wcsinfo = wcsinfo.findframe(starlink.Ast.SkyFrame()) if wcsinfo is None: - raise RuntimeError("The WCS read in does not define a pair of celestial axes" ) + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) return wcsinfo @@ -744,7 +746,7 @@ def _radec(self, x, y, color=None): for line in lines: vals = line.split() if len(vals) != 5: - raise RuntimeError('wcstools xy2sky returned invalid result near %s'%(xy1)) + raise GalSimError('wcstools xy2sky returned invalid result near %s'%(xy1)) ra.append(float(vals[0])) dec.append(float(vals[1])) @@ -782,10 +784,10 @@ def _xy(self, ra, dec, color=None): # However, if there was an error, the J200 might be missing. vals = results.split() if len(vals) < 6: - raise RuntimeError('wcstools sky2xy returned invalid result for %f,%f'%(ra,dec)) + raise GalSimError('wcstools sky2xy returned invalid result for %f,%f'%(ra,dec)) if len(vals) > 6: warnings.warn('wcstools sky2xy indicates that %f,%f is off the image\n'%(ra,dec) + - 'output is %r'%results) + 'output is %r'%results, GalSimWarning) x = float(vals[4]) y = float(vals[5]) @@ -963,11 +965,11 @@ def _read_header(self, header): elif ctype1.startswith('RA---') and ctype2.startswith('DEC--'): flip = False else: - raise RuntimeError("The WCS read in does not define a pair of celestial axes. " + raise GalSimError("The WCS read in does not define a pair of celestial axes. " "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%( ctype1, ctype2)) if ctype1[5:] != ctype2[5:]: - raise RuntimeError("ctype1, ctype2 do not seem to agree on the WCS type") + raise GalSimError("ctype1, ctype2 do not seem to agree on the WCS type") self.wcs_type = ctype1[5:] if self.wcs_type in [ 'TAN', 'TPV', 'TNX', 'TAN-SIP' ]: self.projection = 'gnomonic' @@ -978,7 +980,7 @@ def _read_header(self, header): elif self.wcs_type == 'ARC': self.projection = 'postel' else: - raise RuntimeError("GSFitsWCS cannot read files using WCS type "+self.wcs_type) + raise GalSimError("GSFitsWCS cannot read files using WCS type "+self.wcs_type) crval1 = float(header['CRVAL1']) crval2 = float(header['CRVAL2']) crpix1 = float(header['CRPIX1']) @@ -1145,7 +1147,7 @@ def _read_tnx(self, header): wat1[3] != '=' or not wat1[4].startswith('"') or not wat1[-1].endswith('"') ): - raise RuntimeError("TNX WAT1 was not as expected") + raise GalSimError("TNX WAT1 was not as expected") if ( len(wat2) < 12 or wat2[0] != 'wtype=tnx' or wat2[1] != 'axtype=dec' or @@ -1153,7 +1155,7 @@ def _read_tnx(self, header): wat2[3] != '=' or not wat2[4].startswith('"') or not wat2[-1].endswith('"') ): - raise RuntimeError("TNX WAT2 was not as expected") + raise GalSimError("TNX WAT2 was not as expected") # Break the next bit out into another function, since it is the same for x and y. pv1 = self._parse_tnx_data(wat1[4:]) @@ -1204,7 +1206,7 @@ def _parse_tnx_data(self, data): pv1 = [ float(x) for x in data[8:] ] if len(pv1) != 10: - raise RuntimeError("Wrong number of items found in WAT data") + raise GalSimError("Wrong number of items found in WAT data") # Put these into our matrix formulation. pv = np.array( [ [ pv1[0], pv1[4], pv1[7], pv1[9] ], diff --git a/galsim/gsobject.py b/galsim/gsobject.py index f15b6d9af5a..6aebc6efe1b 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -58,6 +58,7 @@ from . import _galsim from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args +from .errors import GalSimError class GSObject(object): @@ -683,7 +684,7 @@ def xValue(self, *args, **kwargs): Not all GSObject classes can use this method. Classes like Convolution that require a Discrete Fourier Transform to determine the real space values will not do so for a single - position. Instead a RuntimeError will be raised. The xValue() method is available if and + position. Instead a GalSimError will be raised. The xValue() method is available if and only if `obj.is_analytic_x == True`. Users who wish to use the xValue() method for an object that is the convolution of other @@ -1765,7 +1766,7 @@ def drawFFT_makeKImage(self, image): Nk = int(np.ceil(maxk/dk)) * 2 if Nk > self.gsparams.maximum_fft_size: - raise RuntimeError( + raise GalSimError( "drawFFT requires an FFT that is too large: %s. "%Nk + "If you can handle the large FFT, you may update gsparams.maximum_fft_size.") @@ -2087,7 +2088,7 @@ def drawPhot(self, image, gain=1., add_to_image=False, try: photons = self.shoot(thisN, ud) - except RuntimeError: # pragma: no cover + except GalSimError: # pragma: no cover # Give some extra explanation as a warning, then raise the original exception # so the traceback shows as much detail as possible. import warnings diff --git a/galsim/hsm.py b/galsim/hsm.py index 5f7d0cb3154..a14994c8c0f 100644 --- a/galsim/hsm.py +++ b/galsim/hsm.py @@ -57,12 +57,17 @@ import numpy as np + from . import _galsim from .position import PositionD from .bounds import BoundsI from .shear import Shear from .image import Image, ImageI, ImageF, ImageD +from .errors import GalSimError + +class GalSimHSMError(GalSimError): + pass class ShapeData(object): """A class to contain the outputs of using the HSM shape and moments measurement routines. @@ -484,7 +489,7 @@ def _convertMask(image, weight=None, badpix=None): # if no pixels are used, raise an exception if mask.array.sum() == 0: - raise RuntimeError("No pixels are being used!") + raise GalSimError("No pixels are being used!") # finally, return the Image for the weight map return mask @@ -595,7 +600,7 @@ def EstimateShear(gal_image, PSF_image, weight=None, badpix=None, sky_var=0.0, the lower-left pixel is (image.xmin, image.ymin). [default: gal_image.true_center] @param strict Whether to require success. If `strict=True`, then there will be a - `RuntimeError` exception if shear estimation fails. If set to `False`, + `GalSimError` exception if shear estimation fails. If set to `False`, then information about failures will be silently stored in the output ShapeData object. [default: True] @param hsmparams The hsmparams keyword can be used to change the settings used by @@ -622,7 +627,7 @@ def EstimateShear(gal_image, PSF_image, weight=None, badpix=None, sky_var=0.0, return result except RuntimeError as err: if (strict == True): - raise + raise GalSimHSMError(str(err)) else: return ShapeData(error_message = str(err)) @@ -674,7 +679,7 @@ def FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, preci >>> my_moments = my_gaussian_image.FindAdaptiveMom() - then the result will be a RuntimeError due to moment measurement failing because the object is + then the result will be a GalSimError due to moment measurement failing because the object is so large. While the list of all possible settings that can be changed is accessible in the docstring of the HSMParams class, in this case we need to modify `max_amoment` which is the maximum value of the moments in units of pixel^2. The following measurement, using the @@ -703,9 +708,9 @@ def FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, preci is (image.xmin, image.ymin). [default: object_image.true_center] @param strict Whether to require success. If `strict=True`, then there will be a - `RuntimeError` exception if shear estimation fails. If set to `False`, - then information about failures will be silently stored in the output - ShapeData object. [default: True] + `GalSimHSMError` exception if shear estimation fails. If set to + `False`, then information about failures will be silently stored in the + output ShapeData object. [default: True] @param round_moments Use a circular weight function instead of elliptical. [default: False] @param hsmparams The hsmparams keyword can be used to change the settings used by @@ -731,7 +736,7 @@ def FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, preci return result except RuntimeError as err: if (strict == True): - raise + raise GalSimHSMError(str(err)) else: return ShapeData(error_message = str(err)) diff --git a/galsim/image.py b/galsim/image.py index 67a8a3e93f3..3878be6305e 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -21,11 +21,13 @@ from __future__ import division import numpy as np + from . import _galsim from .position import PositionI, PositionD from .bounds import BoundsI, BoundsD from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities +from .errors import GalSimError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -1202,9 +1204,9 @@ def getValue(self, x, y): im(pos) or im(x=x,y=y)) """ if not self.bounds.isDefined(): - raise RuntimeError("Attempt to access values of an undefined image") + raise GalSimError("Attempt to access values of an undefined image") if not self.bounds.includes(x,y): - raise RuntimeError("Attempt to access position %s,%s, not in bounds %s"%(x,y,self.bounds)) + raise GalSimError("Attempt to access position %s,%s, not in bounds %s"%(x,y,self.bounds)) return self._getValue(x,y) def _getValue(self, x, y): @@ -1224,11 +1226,11 @@ def setValue(self, *args, **kwargs): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise RuntimeError("Attempt to set value of an undefined image") + raise GalSimError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise RuntimeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._setValue(pos.x,pos.y,value) def _setValue(self, x, y, value): @@ -1248,11 +1250,11 @@ def addValue(self, *args, **kwargs): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise RuntimeError("Attempt to set value of an undefined image") + raise GalSimError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise RuntimeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._addValue(pos.x,pos.y,value) def _addValue(self, x, y, value): @@ -1267,7 +1269,7 @@ def fill(self, value): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise RuntimeError("Attempt to set values of an undefined image") + raise GalSimError("Attempt to set values of an undefined image") self._fill(value) def _fill(self, value): @@ -1292,7 +1294,7 @@ def invertSelf(self): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise RuntimeError("Attempt to set values of an undefined image") + raise GalSimError("Attempt to set values of an undefined image") self._invertSelf() def _invertSelf(self): diff --git a/galsim/integ.py b/galsim/integ.py index 9ff9a95bed0..228ea3ff927 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -20,10 +20,12 @@ and python image integrators for use in galsim.chromatic """ -from . import _galsim import numpy as np from functools import reduce +from . import _galsim +from .errors import GalSimError + def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): """Integrate a 1-dimensional function from min to max. @@ -55,7 +57,7 @@ def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): if success: return result else: - raise RuntimeError(result) + raise GalSimError(result) def midpt(fvals, x): """Midpoint rule for integration. diff --git a/galsim/interpolant.py b/galsim/interpolant.py index 5cf4167c7e8..bf89d6ed399 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -22,6 +22,7 @@ import math from past.builtins import basestring + from . import _galsim from .gsparams import GSParams from .utilities import lazy_property diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index b3c030c316d..1a1696094f5 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -34,6 +34,7 @@ from .random import BaseDeviate from . import _galsim from . import fits +from .errors import GalSimError class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -338,7 +339,7 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # I think the only things that will mess up if flux == 0 are the # calculateStepK and calculateMaxK functions, and rescaling the flux to some value. if (calculate_stepk or calculate_maxk or flux is not None) and self._image_flux == 0.: - raise RuntimeError("This input image has zero total flux. " + raise GalSimError("This input image has zero total flux. " "It does not define a valid surface brightness profile.") # Process the different options for flux, stepk, maxk diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index 00015ab3740..f04d31ed377 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -20,6 +20,7 @@ """ import numpy as np + from .angle import arcsec, AngleUnit from .position import PositionD, PositionI from .bounds import BoundsD, BoundsI @@ -31,6 +32,7 @@ from . import utilities from . import integ from . import _galsim +from .errors import GalSimError def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -560,7 +562,7 @@ def nRandCallsForBuildGrid(self): (e.g. when calling the function through a Proxy object). """ if not hasattr(self,'ngrid_tot'): - raise RuntimeError("BuildGrid has not been called yet.") + raise GalSimError("BuildGrid has not been called yet.") ntot = 0 # cf. PowerSpectrumRealizer._generate_power_array temp = 2 * np.product( (self.ngrid_tot, self.ngrid_tot//2 +1 ) ) @@ -770,7 +772,7 @@ def _wrap_image(self, im, border=7): # We should throw an exception if the image is smaller than 'border', since at this point # this process doesn't make sense. if im.bounds.xmax - im.bounds.xmin < border: - raise RuntimeError("Periodic wrapping does not work with images this small!") + raise GalSimError("Periodic wrapping does not work with images this small!") expanded_bounds = im.bounds.withBorder(border) # Make new image with those bounds. im_new = ImageD(expanded_bounds, scale=self.grid_spacing) @@ -907,7 +909,7 @@ def getShear(self, pos, units=arcsec, reduced=True, periodic=False): If the input `pos` is given a list/array of positions, they are NumPy arrays. """ if not hasattr(self, 'im_g1'): - raise RuntimeError("PowerSpectrum.buildGrid must be called before getShear") + raise GalSimError("PowerSpectrum.buildGrid must be called before getShear") # Convert to numpy arrays for internal usage: pos_x, pos_y = utilities._convertPositions(pos, units, 'getShear') @@ -1021,7 +1023,7 @@ def getConvergence(self, pos, units=arcsec, periodic=False): If the input `pos` is given a list/array of positions, kappa is a NumPy array. """ if not hasattr(self, 'im_kappa'): - raise RuntimeError("PowerSpectrum.buildGrid must be called before getConvergence") + raise GalSimError("PowerSpectrum.buildGrid must be called before getConvergence") # Convert to numpy arrays for internal usage: pos_x, pos_y = utilities._convertPositions(pos, units, 'getConvergence') @@ -1118,7 +1120,7 @@ def getMagnification(self, pos, units=arcsec, periodic=False): If the input `pos` is given a list/array of positions, mu is a NumPy array. """ if not hasattr(self, 'im_kappa'): - raise RuntimeError("PowerSpectrum.buildGrid must be called before getMagnification") + raise GalSimError("PowerSpectrum.buildGrid must be called before getMagnification") # Convert to numpy arrays for internal usage: pos_x, pos_y = utilities._convertPositions(pos, units, 'getMagnification') @@ -1219,7 +1221,7 @@ def getLensing(self, pos, units=arcsec, periodic=False): If the input `pos` is given a list/array of positions, they are NumPy arrays. """ if not hasattr(self, 'im_kappa'): - raise RuntimeError("PowerSpectrum.buildGrid must be called before getLensing") + raise GalSimError("PowerSpectrum.buildGrid must be called before getLensing") # Convert to numpy arrays for internal usage: pos_x, pos_y = utilities._convertPositions(pos, units, 'getLensing') diff --git a/galsim/nfw_halo.py b/galsim/nfw_halo.py index b503ba39877..a4b5e338b49 100644 --- a/galsim/nfw_halo.py +++ b/galsim/nfw_halo.py @@ -19,6 +19,7 @@ """ import numpy as np + from .position import PositionD from .angle import arcsec from . import integ diff --git a/galsim/noise.py b/galsim/noise.py index 8f63442f22e..b83d51b0e60 100644 --- a/galsim/noise.py +++ b/galsim/noise.py @@ -23,7 +23,9 @@ import numpy as np import math + from .image import Image, ImageD +from .errors import GalSimError def addNoise(self, noise): @@ -547,13 +549,13 @@ def _applyTo(self, image): image.array[:,:] += noise_array.reshape(image.array.shape).astype(image.dtype) def _getVariance(self): - raise RuntimeError("No single variance value for DeviateNoise") + raise GalSimError("No single variance value for DeviateNoise") def _withVariance(self, variance): - raise RuntimeError("Changing the variance is not allowed for DeviateNoise") + raise GalSimError("Changing the variance is not allowed for DeviateNoise") def _withScaledVariance(self, variance): - raise RuntimeError("Changing the variance is not allowed for DeviateNoise") + raise GalSimError("Changing the variance is not allowed for DeviateNoise") def copy(self, rng=None): """Returns a copy of the Deviate noise model. @@ -643,15 +645,15 @@ def copy(self, rng=None): return VariableGaussianNoise(rng, self.var_image) def _getVariance(self): - raise RuntimeError("No single variance value for VariableGaussianNoise") + raise GalSimError("No single variance value for VariableGaussianNoise") def _withVariance(self, variance): - raise RuntimeError("Changing the variance is not allowed for VariableGaussianNoise") + raise GalSimError("Changing the variance is not allowed for VariableGaussianNoise") def _withScaledVariance(self, variance): # This one isn't undefined like withVariance, but it's inefficient. Better to # scale the values in the image before constructing VariableGaussianNoise. - raise RuntimeError("Changing the variance is not allowed for VariableGaussianNoise") + raise GalSimError("Changing the variance is not allowed for VariableGaussianNoise") def __repr__(self): return 'galsim.VariableGaussianNoise(rng=%r, var_image%r)'%(self.rng, self.var_image) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 045d712048c..56858b9e414 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,6 +80,7 @@ from .wcs import PixelScale from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property +from .errors import GalSimError class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -312,7 +313,7 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, else: maximum_fft_size = GSParams().maximum_fft_size if self.npix > maximum_fft_size: - raise RuntimeError("Created pupil plane array that is too large, {0} " + raise GalSimError("Created pupil plane array that is too large, {0} " "If you can handle the large FFT, you may update " "gsparams.maximum_fft_size".format(self.npix)) diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 70871143a36..b577b4f1344 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -17,8 +17,8 @@ # from builtins import range, zip - import numpy as np + from .random import BaseDeviate, GaussianDeviate from .image import Image from .angle import radians diff --git a/galsim/photon_array.py b/galsim/photon_array.py index f98b62c4ec3..e4df943ab35 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -21,9 +21,11 @@ """ import numpy as np + from . import _galsim from .random import UniformDeviate from .angle import radians, arcsec +from .errors import GalSimError # Add on more methods in the python layer @@ -548,7 +550,7 @@ def applyTo(self, photon_array, local_wcs): """ from . import dcr if not photon_array.hasAllocatedWavelengths(): - raise RuntimeError("PhotonDCR requires that wavelengths be set") + raise GalSimError("PhotonDCR requires that wavelengths be set") w = photon_array.wavelength cenx = local_wcs.origin.x diff --git a/galsim/pse.py b/galsim/pse.py index 954582fd2d9..507dedb7c9c 100644 --- a/galsim/pse.py +++ b/galsim/pse.py @@ -27,6 +27,8 @@ import os import sys +from .errors import GalSimError + class PowerSpectrumEstimator(object): """ Class for estimating the shear power spectrum from gridded shears. @@ -181,7 +183,7 @@ def _bin_power(self, C, ell_weight=None): P,_ = np.histogram(self.l_abs, self.bin_edges, weights=C) count,_ = np.histogram(self.l_abs, self.bin_edges) if (count == 0).any(): - raise RuntimeError("Logarithmic bin definition resulted in >=1 empty bin!") + raise GalSimError("Logarithmic bin definition resulted in >=1 empty bin!") return P/count def estimate(self, g1, g2, weight_EE=False, weight_BB=False, theory_func=None): diff --git a/galsim/random.py b/galsim/random.py index fb1736cb354..45812d2df51 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -22,6 +22,7 @@ import numpy as np import weakref + from . import _galsim class BaseDeviate(object): diff --git a/galsim/real.py b/galsim/real.py index 167713093b7..a6d6e0dd678 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -32,12 +32,14 @@ """ +import os +import numpy as np + from .gsobject import GSObject from .chromatic import ChromaticSum from .position import PositionD -import os -import numpy as np from .utilities import doc_inherit +from .errors import GalSimError HST_area = 45238.93416 # Area of HST primary mirror in cm^2 from Synphot User's Guide. @@ -846,7 +848,7 @@ def _parse_files_dirs(file_name, image_dir, sample): 'COSMOS_'+use_sample+'_training_sample') full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): - raise RuntimeError('No RealGalaxy catalog found in %s. '%image_dir + + raise GalSimError('No RealGalaxy catalog found in %s. '%image_dir + 'Run the program galsim_download_cosmos -s %s '%use_sample + 'to download catalog and accompanying image files.') elif image_dir is None: diff --git a/galsim/scene.py b/galsim/scene.py index 1822022dd0c..d330f547b70 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -26,6 +26,7 @@ import os from .real import RealGalaxy, RealGalaxyCatalog +from .errors import GalSimError # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -512,7 +513,7 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= # call the appropriate helper routine for that case. if gal_type == 'real': if chromatic: - raise RuntimeError("Cannot yet make real chromatic galaxies!") + raise GalSimError("Cannot yet make real chromatic galaxies!") gal_list = self._makeReal(indices, noise_pad_size, rng, gsparams) else: # If no pre-selection was done based on radius or flux, then we won't have checked @@ -713,11 +714,11 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se # Note that we can avoid including these in the catalog in the first place by using # `exclusion_level=bad_fits` or `exclusion_level=marginal` when making the catalog. if sstat < 1 or sstat > 4 or sparams[1] <= 0 or sparams[0] <= 0: - raise RuntimeError("Cannot make parametric model for this galaxy!") + raise GalSimError("Cannot make parametric model for this galaxy!") else: use_bulgefit = record['use_bulgefit'] if not use_bulgefit and not record['viable_sersic']: - raise RuntimeError("Cannot make parametric model for this galaxy!") + raise GalSimError("Cannot make parametric model for this galaxy!") if use_bulgefit: # Bulge parameters: @@ -748,7 +749,7 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se # Make sure the bulge-to-total flux ratio is not nonsense. bfrac = bulge_flux/(bulge_flux+disk_flux) if bfrac < 0 or bfrac > 1 or np.isnan(bfrac): - raise RuntimeError("Cannot make parametric model for this galaxy") + raise GalSimError("Cannot make parametric model for this galaxy") # Then combine the two components of the galaxy. if chromatic: diff --git a/galsim/sed.py b/galsim/sed.py index dee960a2cba..6f475dd982e 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -700,7 +700,7 @@ def withMagnitude(self, target_magnitude, bandpass): @returns the new normalized SED. """ if bandpass.zeropoint is None: - raise RuntimeError("Cannot call SED.withMagnitude on this bandpass, because it does not" + raise GalSimError("Cannot call SED.withMagnitude on this bandpass, because it does not" " have a zeropoint. See Bandpass.withZeropoint()") current_magnitude = self.calculateMagnitude(bandpass) norm = 10**(-0.4*(target_magnitude - current_magnitude)) @@ -761,7 +761,7 @@ def calculateMagnitude(self, bandpass): if self.dimensionless: raise TypeError("Cannot calculate magnitude of dimensionless SED.") if bandpass.zeropoint is None: - raise RuntimeError("Cannot do this calculation for a bandpass without an assigned" + raise GalSimError("Cannot do this calculation for a bandpass without an assigned" " zeropoint") flux = self.calculateFlux(bandpass) return -2.5 * np.log10(flux) + bandpass.zeropoint diff --git a/galsim/transform.py b/galsim/transform.py index 06ebcf465b8..761a06aecaa 100644 --- a/galsim/transform.py +++ b/galsim/transform.py @@ -22,6 +22,7 @@ import numpy as np import math import cmath + from . import _galsim from .gsobject import GSObject from .gsparams import GSParams diff --git a/galsim/utilities.py b/galsim/utilities.py index d8e126cdce8..05669e6d851 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -23,9 +23,10 @@ from future.utils import iteritems from builtins import range, object import weakref +import numpy as np +from .errors import GalSimError -import numpy as np def roll2d(image, shape): """Perform a 2D roll (circular shift) on a supplied 2D NumPy array, conveniently. @@ -1147,7 +1148,7 @@ def combine_wave_list(*args): wave_list = np.union1d(wave_list, obj.wave_list) wave_list = wave_list[(wave_list >= blue_limit) & (wave_list <= red_limit)] if blue_limit > red_limit: - raise RuntimeError("Empty wave_list intersection.") + raise GalSimError("Empty wave_list intersection.") # Make sure both limits are included. if len(wave_list) > 0 and (wave_list[0] != blue_limit or wave_list[-1] != red_limit): wave_list = np.union1d([blue_limit, red_limit], wave_list) diff --git a/galsim/wcs.py b/galsim/wcs.py index d0804d046b1..514dcecb9e6 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -48,10 +48,12 @@ """ import numpy as np + from .gsobject import GSObject from .position import PositionI, PositionD from .celestial import CelestialCoord from .shear import Shear +from .errors import GalSimError class BaseWCS(object): """The base class for all other kinds of WCS transformations. @@ -1500,7 +1502,7 @@ def getDecomposition(self): # First we need to see whether or not the transformation includes a flip. The evidence # for a flip is that the determinant is negative. if self._det == 0.: - raise RuntimeError("Transformation is singular") + raise GalSimError("Transformation is singular") elif self._det < 0.: flip = True scale = math.sqrt(-self._det) diff --git a/galsim/wfirst/wfirst_backgrounds.py b/galsim/wfirst/wfirst_backgrounds.py index 087df8dcbd9..11bfbe66bf4 100644 --- a/galsim/wfirst/wfirst_backgrounds.py +++ b/galsim/wfirst/wfirst_backgrounds.py @@ -77,8 +77,8 @@ def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): """ # Check for cached sky level information for this filter. If not, raise exception if not hasattr(bandpass, '_sky_level'): - raise RuntimeError("Only bandpasses returned from galsim.wfirst.getBandpasses() are" - " allowed here!") + raise galsim.GalSimError("Only bandpasses returned from galsim.wfirst.getBandpasses() are" + " allowed here!") # Check for proper type for position, and extract the ecliptic coordinates. if world_pos is None: diff --git a/galsim/wfirst/wfirst_psfs.py b/galsim/wfirst/wfirst_psfs.py index f02283d2345..a242fe8eea8 100644 --- a/galsim/wfirst/wfirst_psfs.py +++ b/galsim/wfirst/wfirst_psfs.py @@ -315,7 +315,7 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): PSF = PSF_dict[SCA] if not isinstance(PSF, galsim.ChromaticOpticalPSF) and \ not isinstance(PSF, galsim.InterpolatedChromaticObject): - raise RuntimeError("Error, PSFs are not ChromaticOpticalPSFs.") + raise galsim.GalSimError("Error, PSFs are not ChromaticOpticalPSFs.") star = galsim.Gaussian(sigma=1.e-8, flux=1.) for bp_name in bandpass_list: diff --git a/galsim/wfirst/wfirst_wcs.py b/galsim/wfirst/wfirst_wcs.py index de9caa66457..237c9f04a0a 100644 --- a/galsim/wfirst/wfirst_wcs.py +++ b/galsim/wfirst/wfirst_wcs.py @@ -157,7 +157,7 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): # Are we allowed to look here? if not allowedPos(world_pos, date): - raise RuntimeError("Error, WFIRST cannot look at this position on this date!") + raise galsim.GalSimError("Error, WFIRST cannot look at this position on this date!") # If position angle was not given, then get the optimal one: if PA is None: @@ -447,8 +447,8 @@ def _parse_sip_file(file): for later calculations. """ if not os.path.exists(file): - raise RuntimeError("Error, cannot find file that should have WFIRST SIP" - " coefficients: %s"%file) + raise galsim.GalSimError("Error, cannot find file that should have WFIRST SIP" + " coefficients: %s"%file) # Parse the file, which comes from wfi_wcs_sip_gen_0.1.c provided by Jeff Kruk. data = np.loadtxt(file, usecols=[0, 3, 4, 5, 6, 7]).transpose() diff --git a/include/galsim/hsm/PSFCorr.h b/include/galsim/hsm/PSFCorr.h index 1120e824458..8ad028f3a13 100644 --- a/include/galsim/hsm/PSFCorr.h +++ b/include/galsim/hsm/PSFCorr.h @@ -237,7 +237,7 @@ namespace hsm { */ class HSMError : public std::runtime_error { public: - HSMError(const std::string& m) : std::runtime_error("HSM Error: " + m) {} + HSMError(const std::string& m) : std::runtime_error(m) {} }; //! @endcond diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 2037fea38f9..cf688bae611 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -1191,14 +1191,14 @@ def test_gsparam(): # getting properly forwarded through the internals of ChromaticObjects. gsparams = galsim.GSParams(maximum_fft_size=16) gal = galsim.Gaussian(fwhm=1, gsparams=gsparams) * bulge_SED - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): gal.drawImage(bandpass) # Repeat, putting the gsparams argument in after the ChromaticObject constructor. gal = galsim.Gaussian(fwhm=1) * bulge_SED psf = galsim.Gaussian(sigma=0.4) final = galsim.Convolve([gal, psf], gsparams=gsparams) - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): final.drawImage(bandpass) do_pickle(final) @@ -1535,7 +1535,7 @@ def __repr__(self): ' when including achromatic transformations after precomputation') # Check that the routine does not interpolate outside of its original bounds. - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): obj_interp.drawImage(bandpass_z) # Make sure it behaves appropriately when asked to apply chromatic transformations after diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index 598aef520b9..c908448e799 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -437,7 +437,7 @@ def test_sersic(): # and would be rather slow. gal6a = galsim.config.BuildGSObject(config, 'gal6')[0] gal6b = galsim.Sersic(n=0.7, half_light_radius=1, flux=50) - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): gsobject_compare(gal6a, gal6b, conv=galsim.Gaussian(sigma=1)) gal7a = galsim.config.BuildGSObject(config, 'gal7')[0] diff --git a/tests/test_config_image.py b/tests/test_config_image.py index c88f7f3a5c1..d57d1817b0f 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -372,12 +372,12 @@ def HighN(config, base, value_type): # If we lower the number of retries, we'll max out and abort the image config['stamp']['retry_failures'] = 10 galsim.config.RemoveCurrent(config) - with assert_raises((ValueError, IndexError, RuntimeError)): + with assert_raises((ValueError, IndexError, galsim.GalSimError)): galsim.config.BuildStamps(nimages, config, do_noise=False) try: with CaptureLog() as cl: galsim.config.BuildStamps(nimages, config, do_noise=False, logger=cl.logger) - except (ValueError,IndexError,RuntimeError): + except (ValueError,IndexError,galsim.GalSimError): pass #print(cl.output) assert "Object 0: Too many exceptions/rejections for this object. Aborting." in cl.output @@ -388,7 +388,7 @@ def HighN(config, base, value_type): try: with CaptureLog() as cl: galsim.config.BuildImages(nimages, config, logger=cl.logger) - except (ValueError,IndexError,RuntimeError): + except (ValueError,IndexError,galsim.GalSimError): pass #print(cl.output) assert "Exception caught when building image" in cl.output @@ -398,7 +398,7 @@ def HighN(config, base, value_type): try: with CaptureLog() as cl: galsim.config.BuildStamps(nimages, config, do_noise=False, logger=cl.logger) - except (ValueError,IndexError,RuntimeError): + except (ValueError,IndexError,galsim.GalSimError): pass #print(cl.output) assert re.search("Process-.: Exception caught when building stamp",cl.output) @@ -406,7 +406,7 @@ def HighN(config, base, value_type): try: with CaptureLog() as cl: galsim.config.BuildImages(nimages, config, logger=cl.logger) - except (ValueError,IndexError,RuntimeError): + except (ValueError,IndexError,galsim.GalSimError): pass #print(cl.output) assert re.search("Process-.: Exception caught when building image",cl.output) diff --git a/tests/test_config_noise.py b/tests/test_config_noise.py index 6fe7fc7410d..b3a6cda71e5 100644 --- a/tests/test_config_noise.py +++ b/tests/test_config_noise.py @@ -604,7 +604,7 @@ def test_whiten(): # If whitening already added too much noise, raise an exception config['image']['noise']['variance'] = 1.e-5 - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.config.BuildStamp(config) # 2. Poisson noise @@ -652,7 +652,7 @@ def test_whiten(): np.testing.assert_almost_equal(im3d.array, im3c.array, decimal=5) config['image']['noise']['sky_level_pixel'] = 1.e-5 - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.config.BuildStamp(config) # 3. CCDNoise @@ -705,7 +705,7 @@ def test_whiten(): config['image']['noise']['sky_level_pixel'] = 1.e-5 config['image']['noise']['read_noise'] = 0 - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.config.BuildStamp(config) # 4. COSMOSNoise @@ -729,7 +729,7 @@ def test_whiten(): config['image']['noise']['variance'] = 1.e-5 del config['_current_cn_tag'] - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.config.BuildStamp(config) diff --git a/tests/test_hsm.py b/tests/test_hsm.py index 3e4c4212ee9..69788973f6f 100644 --- a/tests/test_hsm.py +++ b/tests/test_hsm.py @@ -314,11 +314,11 @@ def test_masks(): assert_raises(ValueError, galsim.hsm.EstimateShear, im, p_im, weight_im) ## excludes all pixels weight_im = galsim.ImageI(imsize, imsize) - assert_raises(RuntimeError, galsim.hsm.FindAdaptiveMom, im, weight_im) - assert_raises(RuntimeError, galsim.hsm.EstimateShear, im, p_im, weight_im) + assert_raises(galsim.GalSimError, galsim.hsm.FindAdaptiveMom, im, weight_im) + assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, im, p_im, weight_im) badpix_im = galsim.ImageI(imsize, imsize, init_value = -1) - assert_raises(RuntimeError, galsim.hsm.FindAdaptiveMom, im, good_weight_im, badpix_im) - assert_raises(RuntimeError, galsim.hsm.EstimateShear, im, p_im, good_weight_im, badpix_im) + assert_raises(galsim.GalSimError, galsim.hsm.FindAdaptiveMom, im, good_weight_im, badpix_im) + assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, im, p_im, good_weight_im, badpix_im) # check moments, shear without mask resm = im.FindAdaptiveMom() @@ -565,9 +565,9 @@ def test_hsmparams(): # Then check failure modes: force it to fail by changing HSMParams. new_params_niter = galsim.hsm.HSMParams(max_mom2_iter = res.moments_n_iter-1) new_params_size = galsim.hsm.HSMParams(max_amoment = 0.3*res.moments_sigma**2) - assert_raises(RuntimeError, galsim.hsm.FindAdaptiveMom, tot_gal_image, + assert_raises(galsim.GalSimError, galsim.hsm.FindAdaptiveMom, tot_gal_image, hsmparams=new_params_niter) - assert_raises(RuntimeError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, + assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, hsmparams=new_params_size) @@ -628,10 +628,10 @@ def test_hsmparams_nodefault(): assert(res.moments_amp > res2.moments_amp),'Amplitudes do not change as expected' # Check that max_amoment, max_ashift work as expected - assert_raises(RuntimeError, + assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, hsmparams=galsim.hsm.HSMParams(max_amoment = 10.)) - assert_raises(RuntimeError, + assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, guess_centroid=galsim.PositionD(47., tot_gal_image.true_center.y), hsmparams=galsim.hsm.HSMParams(max_ashift=0.1)) @@ -668,11 +668,11 @@ def test_strict(): # Check that measuring moments with strict = True results in the expected exception, and that # it is the same one as is stored when running with strict = False. - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.hsm.FindAdaptiveMom(im) try: res2 = im.FindAdaptiveMom() - except RuntimeError as err: + except galsim.GalSimError as err: if str(err) != res.error_message: raise AssertionError("Error messages do not match when running identical tests!") @@ -680,11 +680,11 @@ def test_strict(): res = galsim.hsm.EstimateShear(im, im, strict = False) if res.error_message == '': raise AssertionError("Should have error message stored in case of EstimateShear failure!") - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.hsm.EstimateShear(im, im) try: res2 = galsim.hsm.EstimateShear(im, im) - except RuntimeError as err: + except galsim.GalSimError as err: if str(err) != res.error_message: raise AssertionError("Error messages do not match when running identical tests!") @@ -734,7 +734,7 @@ def test_bounds_centroid(): # Check that we can take a weird/asymmetric sub-image, and it fails because of centroid shift. sub_im = im[galsim.BoundsI(b2.xmin, b2.xmax-100, b2.ymin+27, b2.ymax)] - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.hsm.FindAdaptiveMom(sub_im) # ... and that it passes if we hand in a good centroid guess. Note that this test is a bit less diff --git a/tests/test_image.py b/tests/test_image.py index a830def9168..3245345f030 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -246,29 +246,29 @@ def test_Image_basic(): assert im2_cview[x,y] == value3 # Setting or getting the value outside the bounds should throw an exception. - assert_raises(RuntimeError,im1.setValue,0,0,1) - assert_raises(RuntimeError,im1.__call__,0,0) - assert_raises(RuntimeError,im1.__getitem__,0,0) - assert_raises(RuntimeError,im1.__setitem__,0,0,1) - assert_raises(RuntimeError,im1.view().setValue,0,0,1) - assert_raises(RuntimeError,im1.view().__call__,0,0) - assert_raises(RuntimeError,im1.view().__getitem__,0,0) - assert_raises(RuntimeError,im1.view().__setitem__,0,0,1) - - assert_raises(RuntimeError,im1.setValue,ncol+1,0,1) - assert_raises(RuntimeError,im1.__call__,ncol+1,0) - assert_raises(RuntimeError,im1.view().setValue,ncol+1,0,1) - assert_raises(RuntimeError,im1.view().__call__,ncol+1,0) - - assert_raises(RuntimeError,im1.setValue,0,nrow+1,1) - assert_raises(RuntimeError,im1.__call__,0,nrow+1) - assert_raises(RuntimeError,im1.view().setValue,0,nrow+1,1) - assert_raises(RuntimeError,im1.view().__call__,0,nrow+1) - - assert_raises(RuntimeError,im1.setValue,ncol+1,nrow+1,1) - assert_raises(RuntimeError,im1.__call__,ncol+1,nrow+1) - assert_raises(RuntimeError,im1.view().setValue,ncol+1,nrow+1,1) - assert_raises(RuntimeError,im1.view().__call__,ncol+1,nrow+1) + assert_raises(galsim.GalSimError,im1.setValue,0,0,1) + assert_raises(galsim.GalSimError,im1.__call__,0,0) + assert_raises(galsim.GalSimError,im1.__getitem__,0,0) + assert_raises(galsim.GalSimError,im1.__setitem__,0,0,1) + assert_raises(galsim.GalSimError,im1.view().setValue,0,0,1) + assert_raises(galsim.GalSimError,im1.view().__call__,0,0) + assert_raises(galsim.GalSimError,im1.view().__getitem__,0,0) + assert_raises(galsim.GalSimError,im1.view().__setitem__,0,0,1) + + assert_raises(galsim.GalSimError,im1.setValue,ncol+1,0,1) + assert_raises(galsim.GalSimError,im1.__call__,ncol+1,0) + assert_raises(galsim.GalSimError,im1.view().setValue,ncol+1,0,1) + assert_raises(galsim.GalSimError,im1.view().__call__,ncol+1,0) + + assert_raises(galsim.GalSimError,im1.setValue,0,nrow+1,1) + assert_raises(galsim.GalSimError,im1.__call__,0,nrow+1) + assert_raises(galsim.GalSimError,im1.view().setValue,0,nrow+1,1) + assert_raises(galsim.GalSimError,im1.view().__call__,0,nrow+1) + + assert_raises(galsim.GalSimError,im1.setValue,ncol+1,nrow+1,1) + assert_raises(galsim.GalSimError,im1.__call__,ncol+1,nrow+1) + assert_raises(galsim.GalSimError,im1.view().setValue,ncol+1,nrow+1,1) + assert_raises(galsim.GalSimError,im1.view().__call__,ncol+1,nrow+1) # Also, setting values in something that should be const assert_raises(ValueError,im1.view(make_const=True).setValue,1,1,1) @@ -402,10 +402,10 @@ def test_undefined_image(): assert im11.array.shape == (1,1) assert im11 == im1 - assert_raises(RuntimeError,im1.setValue,0,0,1) - assert_raises(RuntimeError,im1.__call__,0,0) - assert_raises(RuntimeError,im1.view().setValue,0,0,1) - assert_raises(RuntimeError,im1.view().__call__,0,0) + assert_raises(galsim.GalSimError,im1.setValue,0,0,1) + assert_raises(galsim.GalSimError,im1.__call__,0,0) + assert_raises(galsim.GalSimError,im1.view().setValue,0,0,1) + assert_raises(galsim.GalSimError,im1.view().__call__,0,0) do_pickle(im1.bounds) do_pickle(im1) @@ -1884,21 +1884,21 @@ def test_Image_resize(): @timer def test_ConstImage_array_constness(): """Test that Image instances with make_const=True cannot be modified via their .array - attributes, and that if this is attempted a RuntimeError is raised. + attributes, and that if this is attempted a GalSimError is raised. """ for i in range(ntypes): image = galsim.Image(ref_array.astype(types[i]), make_const=True) try: image.array[1, 2] = 666 assert False, "Setting values in a const image.array should have raised an error." - # Apparently older numpy versions might raise a RuntimeError, a ValueError, or a TypeError + # Apparently older numpy versions might raise a GalSimError, a ValueError, or a TypeError # when trying to write to arrays that have writeable=False. # From the numpy 1.7.0 release notes: # Attempting to write to a read-only array (one with # ``arr.flags.writeable`` set to ``False``) used to raise either a - # RuntimeError, ValueError, or TypeError inconsistently, depending on + # GalSimError, ValueError, or TypeError inconsistently, depending on # which code path was taken. It now consistently raises a ValueError. - except (RuntimeError, ValueError, TypeError): + except (galsim.GalSimError, ValueError, TypeError): pass except: assert False, "Unexpected error: "+str(sys.exc_info()[0]) diff --git a/tests/test_integ.py b/tests/test_integ.py index 2789a9c9b48..4a2775fdc1a 100644 --- a/tests/test_integ.py +++ b/tests/test_integ.py @@ -186,7 +186,7 @@ def test_func(x): return x**-2 test_integral, true_result, decimal=test_decimal, verbose=True, err_msg="x^(-2) integral failed across interval [1, inf].") - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.integ.int1d(test_func, 0., 1., test_rel_err, test_abs_err) diff --git a/tests/test_lensing.py b/tests/test_lensing.py index 0b84c523287..dbc10161ac1 100644 --- a/tests/test_lensing.py +++ b/tests/test_lensing.py @@ -237,7 +237,7 @@ def test_shear_variance(): test_ps = galsim.PowerSpectrum(e_power_function=pk_flat_lim, b_power_function=pk_flat_lim) # get shears on 500x500 grid with spacing 0.1 degree rng2 = rng.duplicate() - assert_raises(RuntimeError, test_ps.nRandCallsForBuildGrid) + assert_raises(galsim.GalSimError, test_ps.nRandCallsForBuildGrid) g1, g2 = test_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, rng=rng, units=galsim.degrees) assert g1.shape == (ngrid, ngrid) diff --git a/tests/test_noise.py b/tests/test_noise.py index 9fe74808e3d..f261cd85711 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -91,9 +91,9 @@ def test_deviate_noise(): assert_raises(NotImplementedError, galsim.BaseNoise().withScaledVariance, 23) assert_raises(TypeError, noise.applyTo, 23) assert_raises(NotImplementedError, galsim.BaseNoise().applyTo, testimage) - assert_raises(RuntimeError, noise.getVariance) - assert_raises(RuntimeError, noise.withVariance, 23) - assert_raises(RuntimeError, noise.withScaledVariance, 23) + assert_raises(galsim.GalSimError, noise.getVariance) + assert_raises(galsim.GalSimError, noise.withVariance, 23) + assert_raises(galsim.GalSimError, noise.withScaledVariance, 23) @timer @@ -367,9 +367,9 @@ def test_variable_gaussian_noise(): assert_raises(TypeError, vgn.applyTo, 23) assert_raises(ValueError, vgn.applyTo, galsim.ImageF(3,3)) - assert_raises(RuntimeError, vgn.getVariance) - assert_raises(RuntimeError, vgn.withVariance, 23) - assert_raises(RuntimeError, vgn.withScaledVariance, 23) + assert_raises(galsim.GalSimError, vgn.getVariance) + assert_raises(galsim.GalSimError, vgn.withVariance, 23) + assert_raises(galsim.GalSimError, vgn.withScaledVariance, 23) @timer diff --git a/tests/test_sed.py b/tests/test_sed.py index 990900ad176..d9932d547d4 100644 --- a/tests/test_sed.py +++ b/tests/test_sed.py @@ -378,7 +378,7 @@ def __init__(self, wave_list): np.testing.assert_equal(wave_list, c.wave_list) np.testing.assert_equal(blue_limit, c.blue_limit) np.testing.assert_equal(red_limit, c.red_limit) - with assert_raises(RuntimeError): + with assert_raises(galsim.GalSimError): galsim.utilities.combine_wave_list(a, d) From 145ed04636bbf72143d5eef0289b3d24c2a3a265 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 00:25:17 -0400 Subject: [PATCH 02/96] Use GalSimWarning for GalSim-emitted warnings (#755) --- galsim/__init__.py | 2 +- galsim/chromatic.py | 7 ++++--- galsim/convolve.py | 23 ++++++++++++----------- galsim/correlatednoise.py | 8 ++++---- galsim/deprecated/__init__.py | 9 ++++++--- galsim/detectors.py | 7 +++++-- galsim/errors.py | 5 +++++ galsim/fits.py | 13 +++++++------ galsim/fitswcs.py | 5 +++-- galsim/fouriersqrt.py | 3 ++- galsim/gsobject.py | 13 ++++++++----- galsim/interpolatedimage.py | 5 +++-- galsim/lensing_ps.py | 10 +++++----- galsim/main.py | 5 +++-- galsim/phase_psf.py | 12 +++++++----- galsim/phase_screens.py | 10 +++++++--- galsim/scene.py | 26 +++++++++++++++----------- galsim/sed.py | 1 + galsim/utilities.py | 11 ++++++----- galsim/vonkarman.py | 3 ++- tests/test_convolve.py | 32 ++++++++++++++++---------------- tests/test_correlatednoise.py | 2 +- tests/test_draw.py | 2 +- tests/test_image.py | 6 +++--- tests/test_interpolatedimage.py | 2 +- tests/test_lensing.py | 10 +++++----- tests/test_optics.py | 2 +- tests/test_sum.py | 2 +- tests/test_vonkarman.py | 2 +- 29 files changed, 136 insertions(+), 102 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index e4b299e4cde..ec4e8d90e82 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -100,7 +100,7 @@ from .catalog import Catalog, Dict, OutputCatalog from .scene import COSMOSCatalog from .table import LookupTable, LookupTable2D -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning # Image from .image import Image, ImageS, ImageI, ImageF, ImageD, ImageCF, ImageCD, ImageUS, ImageUI, _Image diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 24688a06c53..3fb22f6a419 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,7 +35,7 @@ from .utilities import lazy_property from . import utilities from . import integ -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -1464,7 +1464,7 @@ def detjac(w): import warnings warnings.warn("Cannot render image with chromatic transformation applied to it " "using interpolation between stored images. Reverting to " - "non-interpolated version.") + "non-interpolated version.", GalSimWarning) obj = obj.deinterpolated self.interpolated = obj.interpolated @@ -1967,7 +1967,8 @@ def __init__(self, *args, **kwargs): import warnings warnings.warn( "Image rendering for this convolution cannot take advantage of " + - "interpolation-related optimization. Will use full profile evaluation.") + "interpolation-related optimization. Will use full profile evaluation.", + GalSimWarning) # Assemble wave_lists self.wave_list, _, _ = utilities.combine_wave_list(self.obj_list) diff --git a/galsim/convolve.py b/galsim/convolve.py index 4e8a703ace9..45d11807cdf 100644 --- a/galsim/convolve.py +++ b/galsim/convolve.py @@ -23,6 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject, ChromaticConvolution from .utilities import lazy_property, doc_inherit +from .errors import GalSimWarning def Convolve(*args, **kwargs): """A function for convolving 2 or more GSObject or ChromaticObject instances. @@ -169,7 +170,7 @@ def __init__(self, *args, **kwargs): msg = """ Doing convolution where all objects have hard edges. There might be some inaccuracies due to ringing in k-space.""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) if real_space: # Can't do real space if nobj > 2 @@ -178,7 +179,7 @@ def __init__(self, *args, **kwargs): msg = """ Real-space convolution of more than 2 objects is not implemented. Switching to DFT method.""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) real_space = False # Also can't do real space if any object is not analytic, so check for that. @@ -190,7 +191,7 @@ def __init__(self, *args, **kwargs): A component to be convolved is not analytic in real space. Cannot use real space convolution. Switching to DFT method.""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) real_space = False break @@ -221,7 +222,7 @@ def _noise(self): if _noise is not None: import warnings warnings.warn("Unable to propagate noise in galsim.Convolution when " - "multiple objects have noise attribute") + "multiple objects have noise attribute", GalSimWarning) break _noise = obj.noise others = [ obj2 for k, obj2 in enumerate(self.obj_list) if k != i ] @@ -483,7 +484,7 @@ def orig_obj(self): return self._orig_obj def _noise(self): if self.orig_obj.noise is not None: import warnings - warnings.warn("Unable to propagate noise in galsim.Deconvolution") + warnings.warn("Unable to propagate noise in galsim.Deconvolution", GalSimWarning) return None def __eq__(self, other): @@ -648,7 +649,7 @@ def __init__(self, obj, real_space=None, gsparams=None): msg = """ Doing auto-convolution of object with hard edges. This might be more accurate and/or faster using real_space=True""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) # Can't do real space if object is not analytic, so check for that. if real_space and not obj.is_analytic_x: @@ -657,7 +658,7 @@ def __init__(self, obj, real_space=None, gsparams=None): Object to be auto-convolved is not analytic in real space. Cannot use real space convolution. Switching to DFT method.""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) real_space = False # Save the construction parameters (as they are at this point) as attributes so they @@ -682,7 +683,7 @@ def real_space(self): return self._real_space def _noise(self): if self.orig_obj.noise is not None: import warnings - warnings.warn("Unable to propagate noise in galsim.AutoConvolution") + warnings.warn("Unable to propagate noise in galsim.AutoConvolution", GalSimWarning) return None def __eq__(self, other): @@ -791,7 +792,7 @@ def __init__(self, obj, real_space=None, gsparams=None): msg = """ Doing auto-correlation of object with hard edges. This might be more accurate and/or faster using real_space=True""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) # Can't do real space if object is not analytic, so check for that. if real_space and not obj.is_analytic_x: @@ -800,7 +801,7 @@ def __init__(self, obj, real_space=None, gsparams=None): Object to be auto-correlated is not analytic in real space. Cannot use real space convolution. Switching to DFT method.""" - warnings.warn(msg) + warnings.warn(msg, GalSimWarning) real_space = False # Save the construction parameters (as they are at this point) as attributes so they @@ -825,7 +826,7 @@ def real_space(self): return self._real_space def _noise(self): if self.orig_obj.noise is not None: import warnings - warnings.warn("Unable to propagate noise in galsim.AutoCorrelation") + warnings.warn("Unable to propagate noise in galsim.AutoCorrelation", GalSimWarning) return None def __eq__(self, other): diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 13073843c74..e81cb4a727b 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -26,7 +26,7 @@ from .random import BaseDeviate from .gsobject import GSObject from . import utilities -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -116,7 +116,7 @@ def __add__(self, other): if not wcs.compatible(self.wcs, other.wcs): import warnings warnings.warn("Adding two CorrelatedNoise objects with incompatible WCS functions.\n"+ - "The result will have the WCS of the first object.") + "The result will have the WCS of the first object.", GalSimWarning) return _BaseCorrelatedNoise(self.rng, self._profile + other._profile, self.wcs) def __sub__(self, other): @@ -124,7 +124,7 @@ def __sub__(self, other): if not wcs.compatible(self.wcs, other.wcs): import warnings warnings.warn("Subtracting two CorrelatedNoise objects with incompatible WCS functions.\n"+ - "The result will have the WCS of the first object.") + "The result will have the WCS of the first object.", GalSimWarning) return _BaseCorrelatedNoise(self.rng, self._profile - other._profile, self.wcs) def __mul__(self, variance_ratio): @@ -1405,7 +1405,7 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i import warnings warnings.warn( "Function getCOSMOSNoise() unable to read FITS image from "+str(file_name)+", "+ - "more information on the error in the following Exception...") + "more information on the error in the following Exception...", GalSimWarning) raise # Then check for negative variance before doing anything time consuming diff --git a/galsim/deprecated/__init__.py b/galsim/deprecated/__init__.py index f202c26658f..ae359f8e69a 100644 --- a/galsim/deprecated/__init__.py +++ b/galsim/deprecated/__init__.py @@ -16,10 +16,13 @@ # and/or other materials provided with the distribution. # +from ..errors import GalSimWarning + # Note: By default python2.7 ignores DeprecationWarnings. Apparently they are really -# for python system deprecations. I think GalSim deprecations are more appropriately -# considered UserWarnings, which are not ignored by default. -class GalSimDeprecationWarning(UserWarning): +# for python system deprecations. GalSim deprecations are thus only subclassed from +# GalSimWarning, not DeprecationWarning. The former are a subclass of UserWarning, +# which are not ignored by default. +class GalSimDeprecationWarning(GalSimWarning): def __init__(self, s): super(GalSimDeprecationWarning, self).__init__(self, s) diff --git a/galsim/detectors.py b/galsim/detectors.py index 3d64ca07c47..2dbc308db33 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -22,7 +22,9 @@ import numpy as np import sys + from .image import Image +from .errors import GalSimWarning def applyNonlinearity(self, NLfunc, *args): """ @@ -140,7 +142,8 @@ def addReciprocityFailure(self, exp_time, alpha, base_flux): if np.any(self.array<0): import warnings - warnings.warn("One or more pixel values are negative and will be set as 'nan'.") + warnings.warn("One or more pixel values are negative and will be set as 'nan'.", + GalSimWarning) p0 = exp_time*base_flux a = alpha/np.log(10) @@ -217,7 +220,7 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ if abs(ipc_kernel.sum() - 1.0) > 10.*np.finfo(ipc_kernel.dtype.type).eps: import warnings warnings.warn("The entries in the IPC kernel did not sum to 1. Scaling the kernel to "\ - +"ensure correct normalization.") + +"ensure correct normalization.", GalSimWarning) IPC_kernel = IPC_kernel/ipc_kernel.sum() # edge_treatment can be 'extend', 'wrap' or 'crop' diff --git a/galsim/errors.py b/galsim/errors.py index 82ef54b7f59..0aeefcf3b1f 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -23,3 +23,8 @@ class GalSimError(RuntimeError): pass + +# Base class for all GalSim-emitted warnings. +class GalSimWarning(UserWarning): + pass + diff --git a/galsim/fits.py b/galsim/fits.py index 13d55cc453c..d8917c7ac63 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -28,7 +28,7 @@ import numpy as np from .image import Image -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning ############################################################################################## @@ -474,7 +474,8 @@ def _check_hdu(hdu, pyfits_compress): 'installed.') elif isinstance(hdu, pyfits.ImageHDU): import warnings - warnings.warn("Expecting a CompImageHDU, but found an uncompressed ImageHDU") + warnings.warn("Expecting a CompImageHDU, but found an uncompressed ImageHDU", + GalSimWarning) else: raise IOError('Found invalid HDU reading FITS file (expected an ImageHDU)') else: @@ -864,8 +865,8 @@ def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto'): data = hdu.data else: import warnings - warnings.warn("No C++ Image template instantiation for data type %s" % dt) - warnings.warn(" Using numpy.float64 instead.") + warnings.warn("No C++ Image template instantiation for data type %s" % dt + + " Using numpy.float64 instead.", GalSimWarning) data = hdu.data.astype(np.float64) image = Image(array=data) @@ -1012,8 +1013,8 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut data = hdu.data else: import warnings - warnings.warn("No C++ Image template instantiation for data type %s" % dt) - warnings.warn(" Using numpy.float64 instead.") + warnings.warn("No C++ Image template instantiation for data type %s" % dt + + " Using numpy.float64 instead.", GalSimWarning) data = hdu.data.astype(np.float64) nimages = data.shape[0] diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 809650eadf7..3aa7ccdae55 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -29,7 +29,7 @@ from .angle import radians, arcsec, degrees, AngleUnit from . import _galsim from . import fits -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning ######################################################################################### # @@ -1698,7 +1698,8 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', # WCS. It defaults to the equivalent of a pixel scale of 1.0 if even these are not present. if not suppress_warning: warnings.warn("All the fits WCS types failed to read "+file_name+". " + - "Using AffineTransform instead, which will not really be correct.") + "Using AffineTransform instead, which will not really be correct.", + GalSimWarning) wcs = AffineTransform._readHeader(header) return wcs diff --git a/galsim/fouriersqrt.py b/galsim/fouriersqrt.py index 05e6a12a401..572bba7b324 100644 --- a/galsim/fouriersqrt.py +++ b/galsim/fouriersqrt.py @@ -23,6 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject from .utilities import lazy_property, doc_inherit +from .errors import GalSimWarning def FourierSqrt(obj, gsparams=None): @@ -106,7 +107,7 @@ def _sbp(self): def _noise(self): if self.orig_obj.noise is not None: import warnings - warnings.warn("Unable to propagate noise in galsim.FourierSqrtProfile") + warnings.warn("Unable to propagate noise in galsim.FourierSqrtProfile", GalSimWarning) return None def __eq__(self, other): diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 6aebc6efe1b..4716e2404eb 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -58,7 +58,7 @@ from . import _galsim from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning class GSObject(object): @@ -1510,7 +1510,8 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N "handle the Pixel convolution for you. If you want to handle the Pixel " "convolution yourself, you can use method=no_pixel. Or if you really meant " "for your profile to include the Pixel and also have GalSim convolve by " - "an _additional_ Pixel, you can suppress this warning by using method=fft.") + "an _additional_ Pixel, you can suppress this warning by using method=fft.", + GalSimWarning) # Some parameters are only relevant for method == 'phot' if method != 'phot' and sensor is None: @@ -1961,7 +1962,7 @@ def _calculate_nphotons(self, n_photons, poisson_flux, max_extra_noise, rng): "flux = %s "%self.flux + "poisson_flux = %s "%poisson_flux + "max_extra_noise = %s "%max_extra_noise + - "g = %s "%g) + "g = %s "%g, GalSimWarning) return 0, 1. return iN, g @@ -2052,7 +2053,8 @@ def drawPhot(self, image, gain=1., add_to_image=False, import warnings warnings.warn( "Warning: drawImage for object with flux == 1, area == 1, and " - "exptime == 1, but n_photons == 0. This will only shoot a single photon.") + "exptime == 1, but n_photons == 0. This will only shoot a single photon.", + GalSimWarning) ud = UniformDeviate(rng) @@ -2094,7 +2096,8 @@ def drawPhot(self, image, gain=1., add_to_image=False, import warnings warnings.warn( "Unable to draw this GSObject with photon shooting. Perhaps it is a "+ - "Deconvolve or is a compound including one or more Deconvolve objects.") + "Deconvolve or is a compound including one or more Deconvolve objects.", + GalSimWarning) raise if g != 1. or thisN != Ntot: diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 1a1696094f5..ff8ead57cea 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -34,7 +34,7 @@ from .random import BaseDeviate from . import _galsim from . import fits -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -877,7 +877,8 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, elif stepk < kimage.scale: import warnings warnings.warn( - "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.") + "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.", + GalSimWarning) self._stepk = kimage.scale else: self._stepk = stepk diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index f04d31ed377..7ff8629eebb 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -32,7 +32,7 @@ from . import utilities from . import integ from . import _galsim -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -975,7 +975,7 @@ def _getSingleShear(self, x, y, ii_g1, ii_g2, periodic): warnings.warn( "Warning: position (%f,%f) not within the bounds "%(x,y) + "of the gridded shear values: " + str(self.bounds) + - ". Returning a shear of (0,0) for this point.") + ". Returning a shear of (0,0) for this point.", GalSimWarning) return 0., 0. else: # Treat this as a periodic box. @@ -1073,7 +1073,7 @@ def _getSingleConvergence(self, x, y, ii_kappa, periodic): warnings.warn( "Warning: position (%f,%f) not within the bounds "%(x,y) + "of the gridded convergence values: " + str(self.bounds) + - ". Returning a convergence of 0 for this point.") + ". Returning a convergence of 0 for this point.", GalSimWarning) return 0. else: # Treat this as a periodic box. @@ -1172,7 +1172,7 @@ def _getSingleMagnification(self, x, y, ii_mu, periodic): warnings.warn( "Warning: position (%f,%f) not within the bounds "%(x,y) + "of the gridded convergence values: " + str(self.bounds) + - ". Returning a magnification of 1 for this point.") + ". Returning a magnification of 1 for this point.", GalSimWarning) return 1. else: # Treat this as a periodic box. @@ -1280,7 +1280,7 @@ def _getSingleLensing(self, x, y, ii_g1, ii_g2, ii_mu, periodic): warnings.warn( "Warning: position (%f,%f) not within the bounds "%(x,y) + "of the gridded values: " + str(self.bounds) + - ". Returning 0 for lensing observables at this point.") + ". Returning 0 for lensing observables at this point.", GalSimWarning) return 0., 0., 1. else: # Treat this as a periodic box. diff --git a/galsim/main.py b/galsim/main.py index 28e7de1e645..b4344a204da 100644 --- a/galsim/main.py +++ b/galsim/main.py @@ -21,12 +21,13 @@ """ from __future__ import print_function - import sys import os import logging import pprint +from .errors import GalSimWarning + def parse_args(): """Handle the command line arguments using either argparse (if available) or optparse. """ @@ -219,7 +220,7 @@ def main(): logging.basicConfig(format="%(message)s", level=logging_level, filename=args.log_file) logger = logging.getLogger('galsim') - logger.warn('Using config file %s', args.config_file) + logger.warn('Using config file %s', args.config_file, GalSimWarning) all_config = ReadConfig(args.config_file, args.file_type, logger) logger.debug('Successfully read in config file.') diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 56858b9e414..eea2ea910a2 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,7 +80,7 @@ from .wcs import PixelScale from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -289,7 +289,7 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, warnings.warn("Input pupil_plane_scale may be too large for good sampling.\n" "Consider decreasing pupil_plane_scale by a factor %f, and/or " "check PhaseScreenPSF outputs for signs of folding in real " - "space."%(1./ratio)) + "space."%(1./ratio), GalSimWarning) else: pupil_plane_scale = good_pupil_scale if pupil_plane_size is not None: @@ -300,7 +300,8 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, warnings.warn("Input pupil_plane_size may be too small for good focal-plane" "sampling.\n" "Consider increasing pupil_plane_size by a factor %f, and/or " - "check PhaseScreenPSF outputs for signs of undersampling."%ratio) + "check PhaseScreenPSF outputs for signs of undersampling."%ratio, + GalSimWarning) else: pupil_plane_size = good_pupil_size self._generate_pupil_plane(circular_pupil, @@ -425,7 +426,8 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good ratio = self.pupil_plane_scale / good_pupil_scale warnings.warn("Input pupil plane image may not be sampled well enough!\n" "Consider increasing sampling by a factor %f, and/or check " - "PhaseScreenPSF outputs for signs of folding in real space."%ratio) + "PhaseScreenPSF outputs for signs of folding in real space."%ratio, + GalSimWarning) if pupil_angle.rad == 0.: self._illuminated = pp_arr.astype(bool) @@ -1290,7 +1292,7 @@ def _finalize(self): "The calculated stepk (%g) for PhaseScreenPSF is smaller "%observed_stepk + "than what was used to build the wavefront (%g). "%specified_stepk + "This could lead to aliasing problems. " + - "Increasing pad_factor is recommended.") + "Increasing pad_factor is recommended.", GalSimWarning) @property def _sbp(self): diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index b577b4f1344..9f5370ecdf0 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -26,6 +26,7 @@ from . import utilities from . import fft from . import zernike +from .errors import GalSimWarning class AtmosphericScreen(object): @@ -214,13 +215,15 @@ def instantiate(self, kmin=0., kmax=np.inf, check=None): import warnings warnings.warn( "Instantiating AtmosphericScreen with kmax != inf " - "may yield surprising results when drawing using Fourier optics.") + "may yield surprising results when drawing using Fourier optics.", + GalSimWarning) if check == 'phot': if self.kmax == np.inf: import warnings warnings.warn( "Instantiating AtmosphericScreen with kmax == inf " - "may yield surprising results when drawing using geometric optics.") + "may yield surprising results when drawing using geometric optics.", + GalSimWarning) # Note the magic number 0.00058 is actually ... wait for it ... @@ -654,7 +657,8 @@ def __init__(self, diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, if aberrations[0] != 0.0: import warnings warnings.warn( - "Detected non-zero value in aberrations[0] -- this value is ignored!") + "Detected non-zero value in aberrations[0] -- this value is ignored!", + GalSimWarning) aberrations = np.array(aberrations) self.aberrations = aberrations diff --git a/galsim/scene.py b/galsim/scene.py index d330f547b70..cbc043af625 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -26,7 +26,7 @@ import os from .real import RealGalaxy, RealGalaxyCatalog -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -215,7 +215,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, warnings.warn( 'You seem to have an old version of the COSMOS parameter file. '+ 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to re-download the COSMOS catalog.') + 'to re-download the COSMOS catalog.', GalSimWarning) # NB. The pyfits FITS_Rec class has a bug where it makes a copy of the full # record array in each record (e.g. in getParametricRecord) and then doesn't @@ -285,7 +285,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, 'File with GalSim selection criteria not found! '+ 'Not all of the requested exclusions will be performed. '+ 'Run the program `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to get the necessary selection file.') + 'to get the necessary selection file.', GalSimWarning) # Finally, impose a cut that the total flux in the postage stamp should be positive, # which excludes a tiny number of galaxies (of order 10 in each sample) with some sky @@ -301,7 +301,8 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, 'the galaxy postage stamps. Exclusion of negative-flux stamps in advance '+ 'cannot be done. '+ 'Run the program `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to get the updated catalog with this information precomputed.') + 'to get the updated catalog with this information precomputed.', + GalSimWarning) if exclusion_level in ['bad_fits', 'marginal']: # This 'exclusion_level' involves eliminating failed parametric fits (bad fit status @@ -351,7 +352,8 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, warnings.warn( 'You seem to have an old version of the COSMOS parameter file. '+ 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to re-download the COSMOS catalog to get faster and more accurate selection.') + 'to re-download the COSMOS catalog to get faster and more accurate selection.', + GalSimWarning) sparams = self.param_cat['sersicfit'] hlr_pix = sparams[:,1] @@ -502,7 +504,8 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= else: if n_random is not None: import warnings - warnings.warn("Ignoring input n_random, since indices were specified!") + warnings.warn("Ignoring input n_random, since indices were specified!", + GalSimWarning) if hasattr(index, '__iter__'): indices = index @@ -526,7 +529,7 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= 'You seem to have an old version of the COSMOS parameter file. '+ 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ 'to re-download the COSMOS catalog '+ - 'and take advantage of pre-computation of many quantities..') + 'and take advantage of pre-computation of many quantities..', GalSimWarning) gal_list = self._makeParametric(indices, chromatic, sersic_prec, gsparams) @@ -544,12 +547,12 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= import warnings warnings.warn( 'Ignoring `deep` argument, because the sample being used already '+ - 'corresponds to a flux limit of F814W<25.2') + 'corresponds to a flux limit of F814W<25.2', GalSimWarning) else: import warnings warnings.warn( 'Ignoring `deep` argument, because the sample being used does not '+ - 'corresponds to a flux limit of F814W<23.5') + 'corresponds to a flux limit of F814W<23.5', GalSimWarning) # Store the orig_index as gal.index regardless of whether we have a RealGalaxy or not. # It gets set by _makeReal, but not by _makeParametric. @@ -589,7 +592,8 @@ def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False): import warnings warnings.warn('Selecting random object without correcting for catalog-level ' 'selection effects. This correction requires the existence of ' - 'real catalog with valid weights in addition to parametric one.') + 'real catalog with valid weights in addition to parametric one.', + GalSimWarning) use_weights = None # By default, get the number of RNG calls. We then decide whether or not to return them @@ -896,7 +900,7 @@ def _makeSingleGalaxy(cosmos_catalog, index, gal_type, noise_pad_size=5, deep=Fa import warnings warnings.warn( 'Ignoring `deep` argument, because the sample being used already '+ - 'corresponds to a flux limit of F814W<25.2') + 'corresponds to a flux limit of F814W<25.2', GalSimWarning) # Store the orig_index as gal.index, since the above RealGalaxy initialization just sets it # as 0. Plus, it isn't set at all if we make a parametric galaxy. And if we are doing the diff --git a/galsim/sed.py b/galsim/sed.py index 6f475dd982e..85457122b59 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -31,6 +31,7 @@ from . import integ from . import dcr from .utilities import WeakMethod, lazy_property, combine_wave_list +from .errors import GalSimError class SED(object): """Object to represent the spectral energy distributions of stars and galaxies. diff --git a/galsim/utilities.py b/galsim/utilities.py index 05669e6d851..4355a8b198e 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -25,7 +25,7 @@ import weakref import numpy as np -from .errors import GalSimError +from .errors import GalSimError, GalSimWarning def roll2d(image, shape): @@ -751,7 +751,7 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): elif suppress_warnings is False: import warnings - warnings.warn("Individual images could not be assigned a WCS automatically.") + warnings.warn("Individual images could not be assigned a WCS automatically.", GalSimWarning) return im_list, offsets @@ -923,7 +923,7 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False elif suppress_warnings is False: import warnings - warnings.warn("Interleaved image could not be assigned a WCS automatically.") + warnings.warn("Interleaved image could not be assigned a WCS automatically.", GalSimWarning) # Assign a possibly non-trivial origin and warn if individual image have different origins. orig = im_list[0].origin @@ -931,8 +931,9 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False for im in im_list[1:]: if not im.origin==orig: # pragma: no cover import warnings - warnings.warn("Images in `im_list' have multiple values for origin. Assigning the \ - origin of the first Image instance in 'im_list' to the interleaved image.") + warnings.warn("Images in `im_list' have multiple values for origin. Assigning the " + "origin of the first Image instance in 'im_list' to the interleaved " + "image.", GalSimWarning) break return img diff --git a/galsim/vonkarman.py b/galsim/vonkarman.py index 3dbe1a05904..6b607049516 100644 --- a/galsim/vonkarman.py +++ b/galsim/vonkarman.py @@ -27,6 +27,7 @@ from .utilities import lazy_property, doc_inherit from .position import PositionD from .angle import arcsec, AngleUnit +from .errors import GalSimWarning class VonKarman(GSObject): @@ -124,7 +125,7 @@ def _sbvk(self): import warnings warnings.warn("VonKarman delta-function component is larger than maxk_threshold. " "Please see docstring for information about this component and how " - "to toggle it.") + "to toggle it.", GalSimWarning) if self._do_delta: sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux-self._delta, self._scale, diff --git a/tests/test_convolve.py b/tests/test_convolve.py index bcfb3e5564f..e78996cfb1c 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -334,17 +334,17 @@ def test_realspace_convolve(): # Check some warnings that should be raised # More than 2 with only hard edges gives a warning either way. (Different warnings though.) - assert_warns(UserWarning, galsim.Convolve, [psf, psf, pixel]) - assert_warns(UserWarning, galsim.Convolve, [psf, psf, pixel], real_space=False) - assert_warns(UserWarning, galsim.Convolve, [psf, psf, pixel], real_space=True) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [psf, psf, pixel]) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [psf, psf, pixel], real_space=False) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [psf, psf, pixel], real_space=True) # 2 with hard edges gives a warning if we ask it not to use real_space - assert_warns(UserWarning, galsim.Convolve, [psf, pixel], real_space=False) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [psf, pixel], real_space=False) # >2 of any kind give a warning if we ask it to use real_space g = galsim.Gaussian(sigma=2) - assert_warns(UserWarning, galsim.Convolve, [g, g, g], real_space=True) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [g, g, g], real_space=True) # non-analytic profiles cannot do real_space d = galsim.Deconvolve(galsim.Gaussian(sigma=2)) - assert_warns(UserWarning, galsim.Convolve, [g, d], real_space=True) + assert_warns(galsim.GalSimWarning, galsim.Convolve, [g, d], real_space=True) # Repeat some of the above for AutoConvolve and AutoCorrelate conv = galsim.AutoConvolve(psf,real_space=True) @@ -357,10 +357,10 @@ def test_realspace_convolve(): do_kvalue(conv,img,"AutoCorrelate Truncated Moffat") do_pickle(conv) - assert_warns(UserWarning, galsim.AutoConvolve, psf, real_space=False) - assert_warns(UserWarning, galsim.AutoConvolve, d, real_space=True) - assert_warns(UserWarning, galsim.AutoCorrelate, psf, real_space=False) - assert_warns(UserWarning, galsim.AutoCorrelate, d, real_space=True) + assert_warns(galsim.GalSimWarning, galsim.AutoConvolve, psf, real_space=False) + assert_warns(galsim.GalSimWarning, galsim.AutoConvolve, d, real_space=True) + assert_warns(galsim.GalSimWarning, galsim.AutoCorrelate, psf, real_space=False) + assert_warns(galsim.GalSimWarning, galsim.AutoCorrelate, d, real_space=True) @timer @@ -778,9 +778,9 @@ def test_convolve_noise(): # to propagate noise properly. (It takes the input noise from the first one.) conv2 = galsim.Convolution(obj1, obj2) conv3 = galsim.Convolution(obj1, obj2, obj3) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert conv2.noise == obj1.noise.convolvedWith(obj2) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert conv3.noise == obj1.noise.convolvedWith(galsim.Convolve(obj2,obj3)) # Other types don't propagate noise and give a warning about it. @@ -788,13 +788,13 @@ def test_convolve_noise(): autoconv = galsim.AutoConvolution(obj2) autocorr = galsim.AutoCorrelation(obj2) four = galsim.FourierSqrt(obj2) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert deconv.noise is None - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert autoconv.noise is None - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert autocorr.noise is None - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): assert four.noise is None obj2.noise = None # Remove obj2 noise for the rest. diff --git a/tests/test_correlatednoise.py b/tests/test_correlatednoise.py index 1bae6b83994..5f7ac92ed66 100644 --- a/tests/test_correlatednoise.py +++ b/tests/test_correlatednoise.py @@ -1085,7 +1085,7 @@ def test_uncorrelated_noise_tracking(): # Convolving two objects with noise works fine, but accessing the resulting noise attribute # leads to a warning. conv_obj = galsim.Convolve(int_im, int_im) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): noise = conv_obj.noise # The noise should be correlated, not just the original UncorrelatedNoise assert isinstance(noise, galsim.correlatednoise._BaseCorrelatedNoise) diff --git a/tests/test_draw.py b/tests/test_draw.py index 884c5fd5a9c..242b2fb58d5 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -1047,7 +1047,7 @@ def test_drawImage_area_exptime(): # Shooting with flux=1 raises a warning. obj1 = obj.withFlux(1) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): obj1.drawImage(method='phot') # But not if we explicitly tell it to shoot 1 photon import warnings diff --git a/tests/test_image.py b/tests/test_image.py index 3245345f030..4ebb4b26480 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -2961,11 +2961,11 @@ def test_FITS_bad_type(): testS_file = os.path.join(datadir, "testS.fits") testMultiS_file = os.path.join(datadir, "test_multiS.fits") testCubeS_file = os.path.join(datadir, "test_cubeS.fits") - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): testS_image = galsim.fits.read(testS_file) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): testMultiS_image_list = galsim.fits.readMulti(testMultiS_file) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): testCubeS_image_list = galsim.fits.readCube(testCubeS_file) np.testing.assert_equal(np.float64, testS_image.array.dtype.type) diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index 11b00eb7d7f..9fe1cf460af 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -1164,7 +1164,7 @@ def test_kroundtrip(): np.testing.assert_almost_equal(b.maxk, c.maxk) # Smaller stepk is overridden. - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): d = galsim.InterpolatedKImage(kim_a, stepk=0.5*b.stepk) np.testing.assert_almost_equal(b.stepk, d.stepk) np.testing.assert_almost_equal(b.maxk, d.maxk) diff --git a/tests/test_lensing.py b/tests/test_lensing.py index dbc10161ac1..801a5c7517a 100644 --- a/tests/test_lensing.py +++ b/tests/test_lensing.py @@ -506,11 +506,11 @@ def test_shear_variance(): "Incorrect shear variance post-interpolation" # Warn for accessing values outside of valid bounds (and not periodic) - assert_warns(UserWarning, test_ps.getShear, pos=(max*2, 0), units='deg') - assert_warns(UserWarning, test_ps.getShear, pos=(max*2, 0), reduced=False, units='deg') - assert_warns(UserWarning, test_ps.getConvergence, pos=(max*2, 0), units='deg') - assert_warns(UserWarning, test_ps.getMagnification, pos=(max*2, 0), units='deg') - assert_warns(UserWarning, test_ps.getLensing, pos=(max*2, 0), units='deg') + assert_warns(galsim.GalSimWarning, test_ps.getShear, pos=(max*2, 0), units='deg') + assert_warns(galsim.GalSimWarning, test_ps.getShear, pos=(max*2, 0), reduced=False, units='deg') + assert_warns(galsim.GalSimWarning, test_ps.getConvergence, pos=(max*2, 0), units='deg') + assert_warns(galsim.GalSimWarning, test_ps.getMagnification, pos=(max*2, 0), units='deg') + assert_warns(galsim.GalSimWarning, test_ps.getLensing, pos=(max*2, 0), units='deg') @timer diff --git a/tests/test_optics.py b/tests/test_optics.py index 76264750d5d..677666cda69 100644 --- a/tests/test_optics.py +++ b/tests/test_optics.py @@ -297,7 +297,7 @@ def test_OpticalPSF_aberrations_kwargs(): with assert_raises(ValueError): galsim.OpticalPSF(lod, aberrations=[0.0]*2) # The first element must be 0. (Just a warning!) - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): galsim.OpticalPSF(lod, aberrations=[0.3]*8) # Cannot provide both aberrations and specific ones by name. with assert_raises(TypeError): diff --git a/tests/test_sum.py b/tests/test_sum.py index 7e93312a882..27ba37d8d2e 100644 --- a/tests/test_sum.py +++ b/tests/test_sum.py @@ -318,7 +318,7 @@ def test_sum_noise(): obj4 = galsim.Gaussian(sigma=3.3) obj4.noise = galsim.UncorrelatedNoise(variance=0.3, scale=0.8) try: - np.testing.assert_warns(UserWarning, galsim.Sum, [obj1, obj4]) + np.testing.assert_warns(galsim.GalSimWarning, galsim.Sum, [obj1, obj4]) except: pass diff --git a/tests/test_vonkarman.py b/tests/test_vonkarman.py index c716605b866..12730148faf 100644 --- a/tests/test_vonkarman.py +++ b/tests/test_vonkarman.py @@ -74,7 +74,7 @@ def test_vk_delta(): """Test a VonKarman with a significant delta-function amplitude""" kwargs = {'lam':1100.0, 'r0':0.8, 'L0':5.0, 'flux':2.2} # Try to see if we can catch the warning first - with assert_warns(UserWarning): + with assert_warns(galsim.GalSimWarning): vk = galsim.VonKarman(**kwargs) kwargs['suppress_warning'] = True From a7350aae14459f6bf7810c898d7c8e3a4f4650ef Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 02:25:16 -0400 Subject: [PATCH 03/96] Use GalSimRangeError when user values are outside of allowed ranges (#755) --- galsim/__init__.py | 2 +- galsim/chromatic.py | 19 ++++++++++--------- galsim/errors.py | 13 ++++++++++--- galsim/fitswcs.py | 14 ++++++++++---- galsim/hsm.py | 10 +++++----- galsim/image.py | 9 +++++---- galsim/interpolatedimage.py | 10 ++++++++-- galsim/moffat.py | 9 +++++++++ galsim/photon_array.py | 4 ++++ galsim/real.py | 4 ++-- galsim/sed.py | 4 ++-- galsim/sensor.py | 4 ++++ galsim/sersic.py | 11 ++++++++++- galsim/shear.py | 13 +++++++------ galsim/spergel.py | 13 +++++++++++++ galsim/transform.py | 3 +++ galsim/vonkarman.py | 12 +++++++++--- tests/test_moffat.py | 8 ++++---- tests/test_shear.py | 16 ++++++++-------- 19 files changed, 124 insertions(+), 54 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index ec4e8d90e82..90af8f12c76 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -100,7 +100,7 @@ from .catalog import Catalog, Dict, OutputCatalog from .scene import COSMOSCatalog from .table import LookupTable, LookupTable2D -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimWarning # Image from .image import Image, ImageS, ImageI, ImageF, ImageD, ImageCF, ImageCD, ImageUS, ImageUI, _Image diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 3fb22f6a419..8e01207bf5e 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,7 +35,7 @@ from .utilities import lazy_property from . import utilities from . import integ -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -1096,8 +1096,9 @@ def _imageAtWavelength(self, wave): """ # First, some wavelength-related sanity checks. if wave < np.min(self.waves) or wave > np.max(self.waves): - raise GalSimError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(wave, np.min(self.waves), np.max(self.waves))) + raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(wave, np.min(self.waves), + np.max(self.waves))) # Figure out where the supplied wavelength is compared to the list of wavelengths on which # images were originally tabulated. @@ -1164,13 +1165,13 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', wave_list, _, _ = utilities.combine_wave_list(wave_objs) if np.min(wave_list) < np.min(self.waves): - raise GalSimError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.min(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(np.min(wave_list), np.min(self.waves), + np.max(self.waves))) if np.max(wave_list) > np.max(self.waves): - raise GalSimError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.max(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" + " %.1f to %.1f nm"%(np.max(wave_list), np.min(self.waves), + np.max(self.waves))) # The integration is carried out using the following two basic principles: # (1) We use linear interpolation between the stored images to get an image at a given diff --git a/galsim/errors.py b/galsim/errors.py index 0aeefcf3b1f..047b700c8dc 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -19,12 +19,19 @@ # Define the class hierarchy for errors and warnings emitted by GalSim that aren't # obviously one of the standard python errors. -# Base class for all GalSim-emitted run-time errors. class GalSimError(RuntimeError): + """The base class for GalSim-specific run-time errors. + """ + pass + +class GalSimRangeError(GalSimError): + """A GalSim-specific exception class indicating that some user-input value is + outside of the allowed range of values. + """ pass -# Base class for all GalSim-emitted warnings. class GalSimWarning(UserWarning): + """The base class for GalSim-emitted warnings. + """ pass - diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 3aa7ccdae55..1471b8989c8 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -966,8 +966,8 @@ def _read_header(self, header): flip = False else: raise GalSimError("The WCS read in does not define a pair of celestial axes. " - "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%( - ctype1, ctype2)) + "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%( + ctype1, ctype2)) if ctype1[5:] != ctype2[5:]: raise GalSimError("ctype1, ctype2 do not seem to agree on the WCS type") self.wcs_type = ctype1[5:] @@ -1335,12 +1335,18 @@ def _radec(self, x, y, color=None): def _invert_pv(self, u, v): # Do this in C++ layer for speed. - return _galsim.InvertPV(u, v, self.pv.ctypes.data) + try: + return _galsim.InvertPV(u, v, self.pv.ctypes.data) + except RuntimeError as err: # pragma: no cover + raise GalSimError(str(err)) def _invert_ab(self, x, y): # Do this in C++ layer for speed. abp_data = 0 if self.abp is None else self.abp.ctypes.data - return _galsim.InvertAB(len(self.ab[0]), x, y, self.ab.ctypes.data, abp_data) + try: + return _galsim.InvertAB(len(self.ab[0]), x, y, self.ab.ctypes.data, abp_data) + except RuntimeError as err: # pragma: no cover + raise GalSimError(str(err)) def _xy(self, ra, dec, color=None): u, v = self.center.project_rad(ra, dec, projection=self.projection) diff --git a/galsim/hsm.py b/galsim/hsm.py index a14994c8c0f..34d27cd73da 100644 --- a/galsim/hsm.py +++ b/galsim/hsm.py @@ -489,7 +489,7 @@ def _convertMask(image, weight=None, badpix=None): # if no pixels are used, raise an exception if mask.array.sum() == 0: - raise GalSimError("No pixels are being used!") + raise GalSimHSMError("No pixels are being used!") # finally, return the Image for the weight map return mask @@ -600,9 +600,9 @@ def EstimateShear(gal_image, PSF_image, weight=None, badpix=None, sky_var=0.0, the lower-left pixel is (image.xmin, image.ymin). [default: gal_image.true_center] @param strict Whether to require success. If `strict=True`, then there will be a - `GalSimError` exception if shear estimation fails. If set to `False`, - then information about failures will be silently stored in the output - ShapeData object. [default: True] + `GalSimHSMError` exception if shear estimation fails. If set to + `False`, then information about failures will be silently stored in the + output ShapeData object. [default: True] @param hsmparams The hsmparams keyword can be used to change the settings used by EstimateShear() when estimating shears; see HSMParams documentation using help(galsim.hsm.HSMParams) for more information. [default: None] @@ -679,7 +679,7 @@ def FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, preci >>> my_moments = my_gaussian_image.FindAdaptiveMom() - then the result will be a GalSimError due to moment measurement failing because the object is + then the result will be a GalSimHSMError due to moment measurement failing because the object is so large. While the list of all possible settings that can be changed is accessible in the docstring of the HSMParams class, in this case we need to modify `max_amoment` which is the maximum value of the moments in units of pixel^2. The following measurement, using the diff --git a/galsim/image.py b/galsim/image.py index 3878be6305e..7b869f3e35f 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -27,7 +27,7 @@ from .bounds import BoundsI, BoundsD from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities -from .errors import GalSimError +from .errors import GalSimError, GalSimRangeError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -1206,7 +1206,8 @@ def getValue(self, x, y): if not self.bounds.isDefined(): raise GalSimError("Attempt to access values of an undefined image") if not self.bounds.includes(x,y): - raise GalSimError("Attempt to access position %s,%s, not in bounds %s"%(x,y,self.bounds)) + raise GalSimRangeError("Attempt to access position %s,%s, not in bounds %s"%( + x,y,self.bounds)) return self._getValue(x,y) def _getValue(self, x, y): @@ -1230,7 +1231,7 @@ def setValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimRangeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._setValue(pos.x,pos.y,value) def _setValue(self, x, y, value): @@ -1254,7 +1255,7 @@ def addValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimRangeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._addValue(pos.x,pos.y,value) def _addValue(self, x, y, value): diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index ff8ead57cea..17b8f3ad639 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -29,7 +29,7 @@ from .image import Image from .bounds import _BoundsI from .position import PositionD -from .interpolant import Quintic, Interpolant +from .interpolant import Quintic, Interpolant, SincInterpolant from .utilities import convert_interpolant, lazy_property, doc_inherit from .random import BaseDeviate from . import _galsim @@ -340,7 +340,7 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # calculateStepK and calculateMaxK functions, and rescaling the flux to some value. if (calculate_stepk or calculate_maxk or flux is not None) and self._image_flux == 0.: raise GalSimError("This input image has zero total flux. " - "It does not define a valid surface brightness profile.") + "It does not define a valid surface brightness profile.") # Process the different options for flux, stepk, maxk self._flux = self._getFlux(flux, normalization) @@ -652,6 +652,12 @@ def _xValue(self, pos): def _kValue(self, kpos): return self._sbp.kValue(kpos._p) + @doc_inherit + def shoot(self, n_photons, rng=None): + if isinstance(self.x_interpolant, SincInterpolant): + raise GalSimError("Photon shooting is not practical with SincInterpolant") + return GSObject.shoot(self, n_photons, rng) + @doc_inherit def _shoot(self, photons, ud): self._sbp.shoot(photons._pa, ud._rng) diff --git a/galsim/moffat.py b/galsim/moffat.py index 394dd5e80ad..2cbab6bec35 100644 --- a/galsim/moffat.py +++ b/galsim/moffat.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimRangeError class Moffat(GSObject): @@ -86,6 +87,12 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t self._flux = float(flux) self._gsparams = GSParams.check(gsparams) + if self._trunc == 0. and self._beta <= 1.1: + raise GalSimRangeError("Moffat profiles with beta <= 1.1 must be truncated") + + if self._trunc < 0.: + raise ValueError("Moffat trunc must be positive") + # Parse the radius options if half_light_radius is not None: if scale_radius is not None or fwhm is not None: @@ -93,6 +100,8 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t "Only one of scale_radius, half_light_radius, or fwhm may be " + "specified for Moffat") self._hlr = float(half_light_radius) + if self._trunc > 0. and self._trunc <= math.sqrt(2.) * self._hlr: + raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.") self._r0 = _galsim.MoffatCalculateSRFromHLR(self._hlr, self._trunc, self._beta) self._fwhm = 0. elif fwhm is not None: diff --git a/galsim/photon_array.py b/galsim/photon_array.py index e4df943ab35..baf72b2ac85 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -209,6 +209,8 @@ def assignAt(self, istart, rhs): def convolve(self, rhs, rng=None): "Convolve this PhotonArray with another." + if rhs.size() != self.size(): + raise GalSimError("PhotonArray.convolve with unequal size arrays") ud = UniformDeviate(rng) self._pa.convolve(rhs._pa, ud._rng) @@ -275,6 +277,8 @@ def addTo(self, image): @returns the total flux of photons the landed inside the image bounds. """ + if not image.bounds.isDefined(): + raise GalSimError("Attempting to PhotonArray::addTo an Image with undefined Bounds") return self._pa.addTo(image._image) @classmethod diff --git a/galsim/real.py b/galsim/real.py index a6d6e0dd678..cba58fdd7ec 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -849,8 +849,8 @@ def _parse_files_dirs(file_name, image_dir, sample): full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): raise GalSimError('No RealGalaxy catalog found in %s. '%image_dir + - 'Run the program galsim_download_cosmos -s %s '%use_sample + - 'to download catalog and accompanying image files.') + 'Run the program galsim_download_cosmos -s %s '%use_sample + + 'to download catalog and accompanying image files.') elif image_dir is None: full_file_name = file_name image_dir = os.path.dirname(file_name) diff --git a/galsim/sed.py b/galsim/sed.py index 85457122b59..77c55867ca8 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -702,7 +702,7 @@ def withMagnitude(self, target_magnitude, bandpass): """ if bandpass.zeropoint is None: raise GalSimError("Cannot call SED.withMagnitude on this bandpass, because it does not" - " have a zeropoint. See Bandpass.withZeropoint()") + " have a zeropoint. See Bandpass.withZeropoint()") current_magnitude = self.calculateMagnitude(bandpass) norm = 10**(-0.4*(target_magnitude - current_magnitude)) return self * norm @@ -763,7 +763,7 @@ def calculateMagnitude(self, bandpass): raise TypeError("Cannot calculate magnitude of dimensionless SED.") if bandpass.zeropoint is None: raise GalSimError("Cannot do this calculation for a bandpass without an assigned" - " zeropoint") + " zeropoint") flux = self.calculateFlux(bandpass) return -2.5 * np.log10(flux) + bandpass.zeropoint diff --git a/galsim/sensor.py b/galsim/sensor.py index 9adcc4d162d..f260f4b27a1 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -67,6 +67,8 @@ def accumulate(self, photons, image, orig_center=None, resume=False): @returns the total flux that fell onto the image. """ + if not image.bounds.isDefined(): + raise GalSimError("Calling accumulate on image with undefined bounds") return photons.addTo(image) def __repr__(self): @@ -275,6 +277,8 @@ def accumulate(self, photons, image, orig_center=PositionI(0,0), resume=False): raise RuntimeError("accumulate called with resume, but provided image does " "not match one used in the previous accumulate call.") self._last_image = image + if not image.bounds.isDefined(): + raise GalSimError("Calling accumulate on image with undefined bounds") return self._silicon.accumulate(photons._pa, self.rng._rng, image._image, orig_center._p, resume) diff --git a/galsim/sersic.py b/galsim/sersic.py index 695a892108b..d88e89a8dd5 100644 --- a/galsim/sersic.py +++ b/galsim/sersic.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimRangeError class Sersic(GSObject): """A class describing a Sersic profile. @@ -196,6 +197,9 @@ class Sersic(GSObject): _is_analytic_x = True _is_analytic_k = True + _minimum_n = 0.3 # Lower bounds has hard limit at ~0.29 + _maximum_n = 6.2 # Upper bounds is just where we have tested that code works well. + # The conversion from hlr to scale radius is complicated for Sersic, especially since we # allow it to be truncated. So we do these calculations in the C++-layer constructor. def __init__(self, n, half_light_radius=None, scale_radius=None, @@ -205,6 +209,11 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, self._trunc = float(trunc) self._gsparams = GSParams.check(gsparams) + if self._n < Sersic._minimum_n: + raise GalSimRangeError("Requested Sersic index, %s, is too small"%self._n) + if self._n > Sersic._maximum_n: + raise GalSimRangeError("Requested Sersic index, %s, is too large"%self._n) + # Parse the radius options if half_light_radius is not None: if scale_radius is not None: @@ -217,7 +226,7 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, self._r0 = self._hlr / self.calculateHLRFactor() else: if self._trunc <= math.sqrt(2.) * self._hlr: - raise ValueError("Sersic trunc must be > sqrt(2) * half_light_radius") + raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius") self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) elif scale_radius is not None: self._r0 = float(scale_radius) diff --git a/galsim/shear.py b/galsim/shear.py index b3fc98458e6..dd7d4001071 100644 --- a/galsim/shear.py +++ b/galsim/shear.py @@ -22,6 +22,7 @@ import numpy as np from .angle import Angle, _Angle, radians +from .errors import GalSimRangeError class Shear(object): """A class to represent shears in a variety of ways. @@ -114,7 +115,7 @@ def __init__(self, *args, **kwargs): g2 = kwargs.pop('g2', 0.) self._g = g1 + 1j * g2 if abs(self._g) > 1.: - raise ValueError("Requested shear exceeds 1: %f"%abs(self._g)) + raise GalSimRangeError("Requested shear exceeds 1: %f"%abs(self._g)) # e1,e2 elif 'e1' in kwargs or 'e2' in kwargs: @@ -122,7 +123,7 @@ def __init__(self, *args, **kwargs): e2 = kwargs.pop('e2', 0.) absesq = e1**2 + e2**2 if absesq > 1.: - raise ValueError("Requested distortion exceeds 1: %s"%np.sqrt(absesq)) + raise GalSimRangeError("Requested distortion exceeds 1: %s"%np.sqrt(absesq)) self._g = (e1 + 1j * e2) * self._e2g(absesq) # eta1,eta2 @@ -144,7 +145,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") g = kwargs.pop('g') if g > 1 or g < 0: - raise ValueError("Requested |shear| is outside [0,1]: %f"%g) + raise GalSimRangeError("Requested |shear| is outside [0,1]: %f"%g) self._g = g * np.exp(2j * beta.rad) # e,beta @@ -158,7 +159,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") e = kwargs.pop('e') if e > 1 or e < 0: - raise ValueError("Requested distortion is outside [0,1]: %f"%e) + raise GalSimRangeError("Requested distortion is outside [0,1]: %f"%e) self._g = self._e2g(e**2) * e * np.exp(2j * beta.rad) # eta,beta @@ -172,7 +173,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") eta = kwargs.pop('eta') if eta < 0: - raise ValueError("Requested eta is below 0: %f"%eta) + raise GalSimRangeError("Requested eta is below 0: %f"%eta) self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) # q,beta @@ -186,7 +187,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") q = kwargs.pop('q') if q <= 0 or q > 1: - raise ValueError("Cannot use requested axis ratio of %f!"%q) + raise GalSimRangeError("Cannot use requested axis ratio of %f!"%q) eta = -np.log(q) self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) diff --git a/galsim/spergel.py b/galsim/spergel.py index a76b68bae1e..f06eead30aa 100644 --- a/galsim/spergel.py +++ b/galsim/spergel.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimRangeError class Spergel(GSObject): @@ -103,12 +104,24 @@ class Spergel(GSObject): _is_analytic_x = True _is_analytic_k = True + # Constrain range of allowed Spergel index nu. Spergel (2010) Table 1 lists values of nu + # from -0.9 to +0.85. We found that nu = -0.9 is too tricky for the GKP integrator to + # handle, however, so the lower limit is -0.85 instead. The upper limit is set by the + # cyl_bessel_k function, which runs into overflow errors for nu larger than about 4.0. + _minimum_nu = -0.85 + _maximum_nu = 4.0 + def __init__(self, nu, half_light_radius=None, scale_radius=None, flux=1., gsparams=None): self._nu = float(nu) self._flux = float(flux) self._gsparams = GSParams.check(gsparams) + if self._nu < Spergel._minimum_nu: + raise GalSimRangeError("Requested Spergel index, %s, is too small"%self._nu) + if self._nu > Spergel._maximum_nu: + raise GalSimRangeError("Requested Spergel index, %s, is too large"%self._nu) + # Parse the radius options if half_light_radius is not None: if scale_radius is not None: diff --git a/galsim/transform.py b/galsim/transform.py index 761a06aecaa..6a228bb7cb0 100644 --- a/galsim/transform.py +++ b/galsim/transform.py @@ -28,6 +28,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit, WeakMethod from .position import PositionD +from .errors import GalSimError def Transform(obj, jac=(1.,0.,0.,1.), offset=PositionD(0.,0.), flux_ratio=1., gsparams=None): """A function for transforming either a GSObject or ChromaticObject. @@ -161,6 +162,8 @@ def _flux(self): @lazy_property def _sbp(self): + if self._det == 0.: + raise GalSimError("Transformation has a degenerate Jacobian") dudx, dudy, dvdx, dvdy = self._jac.ravel() return _galsim.SBTransform(self._original._sbp, dudx, dudy, dvdx, dvdy, self._offset._p, self._flux_ratio, self.gsparams._gsp) diff --git a/galsim/vonkarman.py b/galsim/vonkarman.py index 6b607049516..56e5cf0e160 100644 --- a/galsim/vonkarman.py +++ b/galsim/vonkarman.py @@ -27,7 +27,7 @@ from .utilities import lazy_property, doc_inherit from .position import PositionD from .angle import arcsec, AngleUnit -from .errors import GalSimWarning +from .errors import GalSimError, GalSimWarning class VonKarman(GSObject): @@ -117,8 +117,14 @@ def __init__(self, lam, r0, L0=25.0, flux=1, scale_unit=arcsec, @lazy_property def _sbvk(self): - sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux, - self._scale, self._do_delta, self._gsparams._gsp) + try: + sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux, + self._scale, self._do_delta, self._gsparams._gsp) + except RuntimeError as err: # pragma: no cover + # There are apparently a couple possible failure modes that can be found in the + # C++ layer. Turn them into GalSimErrors. + raise GalSimError(std(err)) + self._delta = sbvk.getDelta() if not self._suppress: if self._delta > self._gsparams.maxk_threshold: diff --git a/tests/test_moffat.py b/tests/test_moffat.py index f074c1812ce..4754639d3d5 100644 --- a/tests/test_moffat.py +++ b/tests/test_moffat.py @@ -111,10 +111,10 @@ def test_moffat(): np.testing.assert_almost_equal(moffat.xValue(cen), moffat.max_sb) # Should raise an exception if >=2 radii are provided. - assert_raises(TypeError, galsim.Moffat, beta=1, scale_radius=3, half_light_radius=1, fwhm=2) - assert_raises(TypeError, galsim.Moffat, beta=1, half_light_radius=1, fwhm=2) - assert_raises(TypeError, galsim.Moffat, beta=1, scale_radius=3, fwhm=2) - assert_raises(TypeError, galsim.Moffat, beta=1, scale_radius=3, half_light_radius=1) + assert_raises(TypeError, galsim.Moffat, beta=3, scale_radius=3, half_light_radius=1, fwhm=2) + assert_raises(TypeError, galsim.Moffat, beta=3, half_light_radius=1, fwhm=2) + assert_raises(TypeError, galsim.Moffat, beta=3, scale_radius=3, fwhm=2) + assert_raises(TypeError, galsim.Moffat, beta=3, scale_radius=3, half_light_radius=1) @timer diff --git a/tests/test_shear.py b/tests/test_shear.py index 40344f4403a..bb3d3a82049 100644 --- a/tests/test_shear.py +++ b/tests/test_shear.py @@ -177,17 +177,17 @@ def test_shear_initialization(): assert_raises(TypeError,galsim.Shear,g1=0.3,e2=0.2) assert_raises(TypeError,galsim.Shear,eta1=0.3,beta=0.*galsim.degrees) assert_raises(TypeError,galsim.Shear,q=0.3) - assert_raises(ValueError,galsim.Shear,q=1.3,beta=0.*galsim.degrees) - assert_raises(ValueError,galsim.Shear,g1=0.9,g2=0.6) - assert_raises(ValueError,galsim.Shear,e=-1.3,beta=0.*galsim.radians) - assert_raises(ValueError,galsim.Shear,e=1.3,beta=0.*galsim.radians) - assert_raises(ValueError,galsim.Shear,e1=0.7,e2=0.9) + assert_raises(galsim.GalSimRangeError,galsim.Shear,q=1.3,beta=0.*galsim.degrees) + assert_raises(galsim.GalSimRangeError,galsim.Shear,g1=0.9,g2=0.6) + assert_raises(galsim.GalSimRangeError,galsim.Shear,e=-1.3,beta=0.*galsim.radians) + assert_raises(galsim.GalSimRangeError,galsim.Shear,e=1.3,beta=0.*galsim.radians) + assert_raises(galsim.GalSimRangeError,galsim.Shear,e1=0.7,e2=0.9) assert_raises(TypeError,galsim.Shear,g=0.5) assert_raises(TypeError,galsim.Shear,e=0.5) assert_raises(TypeError,galsim.Shear,eta=0.5) - assert_raises(ValueError,galsim.Shear,eta=-0.5,beta=0.*galsim.radians) - assert_raises(ValueError,galsim.Shear,g=1.3,beta=0.*galsim.radians) - assert_raises(ValueError,galsim.Shear,g=-0.3,beta=0.*galsim.radians) + assert_raises(galsim.GalSimRangeError,galsim.Shear,eta=-0.5,beta=0.*galsim.radians) + assert_raises(galsim.GalSimRangeError,galsim.Shear,g=1.3,beta=0.*galsim.radians) + assert_raises(galsim.GalSimRangeError,galsim.Shear,g=-0.3,beta=0.*galsim.radians) assert_raises(TypeError,galsim.Shear,e=0.3,beta=0.) assert_raises(TypeError,galsim.Shear,eta=0.3,beta=0.) assert_raises(TypeError,galsim.Shear,randomkwarg=0.1) From aa6bf250f639949bf039f3df257ce2b4ee1dca42 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 08:30:01 -0400 Subject: [PATCH 04/96] Change default maximum_fft_size to 8192 (#755) --- CHANGELOG.md | 30 +++++++++++++++--------------- galsim/gsparams.py | 4 ++-- include/galsim/GSParams.h | 2 +- tests/galsim_test_helpers.py | 15 +++++++++++++++ tests/test_airy.py | 15 --------------- tests/test_box.py | 16 ---------------- tests/test_convolve.py | 15 --------------- tests/test_deltafunction.py | 15 --------------- tests/test_exponential.py | 15 --------------- tests/test_fouriersqrt.py | 15 --------------- tests/test_gaussian.py | 16 ---------------- tests/test_kolmogorov.py | 16 ---------------- tests/test_moffat.py | 16 ---------------- tests/test_sersic.py | 16 ---------------- tests/test_shapelet.py | 7 +++---- tests/test_spergel.py | 16 ---------------- tests/test_sum.py | 16 ---------------- tests/test_transforms.py | 16 ---------------- 18 files changed, 36 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67501b16bdf..8fa9d1b3c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,10 @@ API Changes Bug Fixes --------- +======= +- Removed the lsst module, which depended on the LSST stack and had gotten + quite out of sync and broken. (#964) +>>>>>>> Change default maximum_fft_size to 8192 Deprecated Features @@ -66,18 +70,14 @@ Deprecated Features New Features ------------ -- Added Zernike submodule. (#832, #951) -- Updated PhaseScreen wavefront and wavefront_gradient methods to accept `None` - as a valid time argument, which means to use the internally stored time in - the screen(s). (#864) -- Added SecondKick profile GSObject. (#864) -- Updated PhaseScreenPSFs to automatically include SecondKick objects when - being drawn with geometric photon shooting. (#864) -- Added option to use circular weight function in HSM adaptive moments code. - (#917) -- Added VonKarman profile GSObject. (#940) -- Added PhotonDCR surface op to apply DCR for photon shooting. (#955) -- Added astropy units as allowed values of wave_type in Bandpass. (#955) -- Added ability to get net pixel areas from the Silicon code for a given flux - image. (#963) -- Added ability to transpose the meaning of (x,y) in the Silicon class. (#963) +- Added a new class hierarchy for exceptions raised by GalSim with the base + class `GalSimError`, a subclass of `RuntimeError`. This provides a hook for + adding sub-classes, which may provide more specific information about the + nature of an error. So far, sub-classes include GalSimHSMError for errors + during HSM measurements and GalSimRangeError for attempted use of input + parameters outside of allowed ranges. (#755) +- Changed the type of warnings raised by GalSim to GalSimWarning, which is + a subclass of UserWarning. (#755) +- Changed the default maximum_fft_size in GSParams to 8192 from 4096. This + increases the potential memory used by an FFT when drawing an object with + an FFT from 256 MB to 1 GB. (#755) diff --git a/galsim/gsparams.py b/galsim/gsparams.py index 195225dae9a..2d083a1e0dc 100644 --- a/galsim/gsparams.py +++ b/galsim/gsparams.py @@ -51,7 +51,7 @@ class GSParams(object): will raise an exception indicating that the image is too large, which is often a sign of an error in the user's code. However, if you have the memory to handle it, you can raise this limit to - allow the calculation to happen. [default: 4096] + allow the calculation to happen. [default: 8192] @param folding_threshold This sets a maximum amount of real space folding that is allowed, an effect caused by the periodic nature of FFTs. FFTs implicitly use periodic boundary conditions, and a profile specified on a @@ -123,7 +123,7 @@ class GSParams(object): of probability are considered ok to use with the dominant-sampling algorithm. [default: 1.e-4] """ - def __init__(self, minimum_fft_size=128, maximum_fft_size=4096, + def __init__(self, minimum_fft_size=128, maximum_fft_size=8192, folding_threshold=5.e-3, stepk_minimum_hlr=5, maxk_threshold=1.e-3, kvalue_accuracy=1.e-5, xvalue_accuracy=1.e-5, table_spacing=1, realspace_relerr=1.e-4, realspace_abserr=1.e-6, diff --git a/include/galsim/GSParams.h b/include/galsim/GSParams.h index dd2d169086c..bc5dcc9c728 100644 --- a/include/galsim/GSParams.h +++ b/include/galsim/GSParams.h @@ -123,7 +123,7 @@ namespace galsim { */ GSParams() : minimum_fft_size(128), - maximum_fft_size(4096), + maximum_fft_size(8192), folding_threshold(5.e-3), stepk_minimum_hlr(5.), maxk_threshold(1.e-3), diff --git a/tests/galsim_test_helpers.py b/tests/galsim_test_helpers.py index f26054c451e..fb7105b8f28 100644 --- a/tests/galsim_test_helpers.py +++ b/tests/galsim_test_helpers.py @@ -34,6 +34,21 @@ # This file has some helper functions that are used by tests from multiple files to help # avoid code duplication. +# These are the default GSParams used when unspecified. We'll check that specifying +# these explicitly produces the same results. +default_params = galsim.GSParams( + minimum_fft_size = 128, + maximum_fft_size = 8192, + folding_threshold = 5.e-3, + maxk_threshold = 1.e-3, + kvalue_accuracy = 1.e-5, + xvalue_accuracy = 1.e-5, + shoot_accuracy = 1.e-5, + realspace_relerr = 1.e-4, + realspace_abserr = 1.e-6, + integration_relerr = 1.e-6, + integration_abserr = 1.e-8) + def gsobject_compare(obj1, obj2, conv=None, decimal=10): """Helper function to check that two GSObjects are equivalent """ diff --git a/tests/test_airy.py b/tests/test_airy.py index 9af812c3ae7..6d60894fac2 100644 --- a/tests/test_airy.py +++ b/tests/test_airy.py @@ -28,21 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_airy(): """Test the generation of a specific Airy profile against a known result. diff --git a/tests/test_box.py b/tests/test_box.py index b0d8e4e46e3..a959b93efb5 100644 --- a/tests/test_box.py +++ b/tests/test_box.py @@ -28,22 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - - @timer def test_box(): """Test the generation of a specific box profile against a known result. diff --git a/tests/test_convolve.py b/tests/test_convolve.py index e78996cfb1c..d331ee5cffc 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -27,21 +27,6 @@ imgdir = os.path.join(".", "SBProfile_comparison_images") # Directory containing the reference # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_convolve(): """Test the convolution of a Moffat and a Box profile against a known result. diff --git a/tests/test_deltafunction.py b/tests/test_deltafunction.py index ba81248faaa..fb3eb63c194 100644 --- a/tests/test_deltafunction.py +++ b/tests/test_deltafunction.py @@ -25,21 +25,6 @@ from galsim_test_helpers import * -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_deltaFunction(): """Test the generation of a Delta function profile diff --git a/tests/test_exponential.py b/tests/test_exponential.py index 118fb151605..7945c10696d 100644 --- a/tests/test_exponential.py +++ b/tests/test_exponential.py @@ -28,21 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_exponential(): """Test the generation of a specific exp profile against a known result. diff --git a/tests/test_fouriersqrt.py b/tests/test_fouriersqrt.py index 24caec43420..9c40c08a4a2 100644 --- a/tests/test_fouriersqrt.py +++ b/tests/test_fouriersqrt.py @@ -28,21 +28,6 @@ # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_fourier_sqrt(): """Test that the FourierSqrt operator is the inverse of auto-convolution. diff --git a/tests/test_gaussian.py b/tests/test_gaussian.py index d9020353b47..9e1d0d48799 100644 --- a/tests/test_gaussian.py +++ b/tests/test_gaussian.py @@ -29,22 +29,6 @@ # images. -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - - @timer def test_gaussian(): """Test the generation of a specific Gaussian profile against a known result. diff --git a/tests/test_kolmogorov.py b/tests/test_kolmogorov.py index 2307347cebc..d3d1d797815 100644 --- a/tests/test_kolmogorov.py +++ b/tests/test_kolmogorov.py @@ -28,22 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. - -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_kolmogorov(): """Test the generation of a specific Kolmogorov profile against a known result. diff --git a/tests/test_moffat.py b/tests/test_moffat.py index 4754639d3d5..68991b2f68f 100644 --- a/tests/test_moffat.py +++ b/tests/test_moffat.py @@ -28,22 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. - -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_moffat(): """Test the generation of a specific Moffat profile against a known result. diff --git a/tests/test_sersic.py b/tests/test_sersic.py index 5b097c10e82..103716917e3 100644 --- a/tests/test_sersic.py +++ b/tests/test_sersic.py @@ -28,22 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. - -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_sersic(): """Test the generation of a specific Sersic profile against a known result. diff --git a/tests/test_shapelet.py b/tests/test_shapelet.py index d60a4439431..4eda4371cce 100644 --- a/tests/test_shapelet.py +++ b/tests/test_shapelet.py @@ -24,10 +24,9 @@ import galsim from galsim_test_helpers import * -imgdir = os.path.join(".", "SBProfile_comparison_images") # Directory containing the reference - # images. - -# define a series of tests +path, filename = os.path.split(__file__) +imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference + # images. @timer def test_shapelet_gaussian(): diff --git a/tests/test_spergel.py b/tests/test_spergel.py index 2bda867293f..647146ed947 100644 --- a/tests/test_spergel.py +++ b/tests/test_spergel.py @@ -28,22 +28,6 @@ imgdir = os.path.join(path, "SBProfile_comparison_images") # Directory containing the reference # images. - -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_spergel(): """Test the generation of a specific Spergel profile against a known result. diff --git a/tests/test_sum.py b/tests/test_sum.py index 27ba37d8d2e..f755181d434 100644 --- a/tests/test_sum.py +++ b/tests/test_sum.py @@ -27,22 +27,6 @@ imgdir = os.path.join(".", "SBProfile_comparison_images") # Directory containing the reference # images. - -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - @timer def test_add(): """Test the addition of two rescaled Gaussian profiles against a known double Gaussian result. diff --git a/tests/test_transforms.py b/tests/test_transforms.py index b7114f0546b..55548366dac 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -27,25 +27,9 @@ imgdir = os.path.join(".", "SBProfile_comparison_images") # Directory containing the reference # images. - # for flux normalization tests test_flux = 1.8 -# These are the default GSParams used when unspecified. We'll check that specifying -# these explicitly produces the same results. -default_params = galsim.GSParams( - minimum_fft_size = 128, - maximum_fft_size = 4096, - folding_threshold = 5.e-3, - maxk_threshold = 1.e-3, - kvalue_accuracy = 1.e-5, - xvalue_accuracy = 1.e-5, - shoot_accuracy = 1.e-5, - realspace_relerr = 1.e-4, - realspace_abserr = 1.e-6, - integration_relerr = 1.e-6, - integration_abserr = 1.e-8) - # Some parameters used in the two unit tests test_integer_shift_fft and test_integer_shift_photon: test_sigma = 1.8 test_hlr = 1.8 From f1641681431045d60b59bec51a84f9cd6a673d40 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 10:56:01 -0400 Subject: [PATCH 05/96] Extend GalSimRangeError to more uses that had been ValueError (#755) --- galsim/bandpass.py | 22 ++++++++++++---------- galsim/chromatic.py | 15 ++++++--------- galsim/correlatednoise.py | 8 +++++--- galsim/detectors.py | 17 ++++++++++------- galsim/errors.py | 18 +++++++++++++++--- galsim/gsobject.py | 10 +++++----- galsim/image.py | 6 +++--- galsim/inclined.py | 21 +++++++++++---------- galsim/integ.py | 4 ++-- galsim/interpolatedimage.py | 12 +++++++----- galsim/moffat.py | 9 +++++---- galsim/nfw_halo.py | 5 +++-- galsim/phase_psf.py | 5 ++--- galsim/phase_screens.py | 4 ++-- galsim/photon_array.py | 8 ++++---- galsim/random.py | 6 +++--- galsim/randwalk.py | 8 ++++---- galsim/sed.py | 12 ++++++------ galsim/sersic.py | 9 ++++++--- galsim/shear.py | 12 ++++++------ galsim/spergel.py | 6 ++++-- galsim/table.py | 9 +++++---- galsim/utilities.py | 4 ++-- 23 files changed, 128 insertions(+), 102 deletions(-) diff --git a/galsim/bandpass.py b/galsim/bandpass.py index 6f6721b6c9b..12656a0f7f1 100644 --- a/galsim/bandpass.py +++ b/galsim/bandpass.py @@ -29,6 +29,7 @@ from . import integ from . import meta_data from .utilities import WeakMethod, combine_wave_list +from .errors import GalSimRangeError class Bandpass(object): """Simple bandpass object, which models the transmission fraction of incident light as a @@ -111,7 +112,8 @@ def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, # it can be supplied directly as a constructor argument. if blue_limit is not None and red_limit is not None and blue_limit >= red_limit: - raise ValueError("blue_limit must be less than red_limit") + raise GalSimRangeError("blue_limit must be less than red_limit", + blue_limit, None, red_limit) self.blue_limit = blue_limit # These may change as we go through this. self.red_limit = red_limit self.zeropoint = zeropoint @@ -175,11 +177,11 @@ def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, self.wave_list = np.array(self._tp.getArgs())/self.wave_factor # Make sure that blue_limit and red_limit are within LookupTable region of support. if self.blue_limit < (self._tp.x_min/self.wave_factor): - raise ValueError("Cannot set blue_limit to be less than throughput " - + "LookupTable.x_min") + raise GalSimRangeError("Cannot set blue_limit to be less than throughput x_min", + self.blue_limit, self._tp.x_min, self._tp.x_max) if self.red_limit > (self._tp.x_max/self.wave_factor): - raise ValueError("Cannot set red_limit to be greater than throughput " - + "LookupTable.x_max") + raise GalSimRangeError("Cannot set red_limit to be greater than throughput x_max", + self.red_limit, self._tp.x_min, self._tp.x_max) # Remove any values that are outside the limits self.wave_list = self.wave_list[np.logical_and(self.wave_list >= self.blue_limit, self.wave_list <= self.red_limit) ] @@ -398,7 +400,7 @@ def withZeropoint(self, zeropoint): vegafile = os.path.join(meta_data.share_dir, "SEDs", "vega.txt") sed = SED(vegafile, wave_type='nm', flux_type='flambda') else: - raise ValueError("Do not recognize Zeropoint string {0}.".format(zeropoint)) + raise ValueError("Unrecognized Zeropoint string {0}.".format(zeropoint)) zeropoint = sed # Convert `zeropoint` from galsim.SED to float @@ -475,14 +477,14 @@ def truncate(self, blue_limit=None, red_limit=None, relative_throughput=None, blue_limit = self.blue_limit else: if blue_limit < self.blue_limit: - raise ValueError("Supplied blue_limit (%f) is bluer than the original (%f)!"% - (blue_limit, self.blue_limit)) + raise GalSimRangeError("Supplied blue_limit may not be bluer than the original.", + blue_limit, self.blue_limit, self.red_limit) if red_limit is None: red_limit = self.red_limit else: if red_limit > self.red_limit: - raise ValueError("Supplied red_limit (%f) is redder than the original (%f)!"% - (red_limit, self.red_limit)) + raise GalSimRangeError("Supplied red_limit may not be redder than the original.", + red_limit, self.blue_limit, self.red_limit) wave_list = self.wave_list if len(self.wave_list) > 0: diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 8e01207bf5e..2c13349b1fc 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -1096,9 +1096,8 @@ def _imageAtWavelength(self, wave): """ # First, some wavelength-related sanity checks. if wave < np.min(self.waves) or wave > np.max(self.waves): - raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(wave, np.min(self.waves), - np.max(self.waves))) + raise GalSimRangeError("Requested wavelength is outside the allowed range.", + wave, np.min(self.waves), np.max(self.waves)) # Figure out where the supplied wavelength is compared to the list of wavelengths on which # images were originally tabulated. @@ -1165,13 +1164,11 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', wave_list, _, _ = utilities.combine_wave_list(wave_objs) if np.min(wave_list) < np.min(self.waves): - raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.min(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimRangeError("Requested wavelength is outside the allowed range.", + wave_list, np.min(self.waves), np.max(self.waves)) if np.max(wave_list) > np.max(self.waves): - raise GalSimRangeError("Requested wavelength %.1f is outside the allowed range:" - " %.1f to %.1f nm"%(np.max(wave_list), np.min(self.waves), - np.max(self.waves))) + raise GalSimRangeError("Requested wavelength is outside the allowed range.", + wave_list, np.min(self.waves), np.max(self.waves)) # The integration is carried out using the following two basic principles: # (1) We use linear interpolation between the stored images to get an image at a given diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index e81cb4a727b..eb62e2c64a9 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -26,7 +26,7 @@ from .random import BaseDeviate from .gsobject import GSObject from . import utilities -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimWarning def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -1410,7 +1410,8 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i # Then check for negative variance before doing anything time consuming if variance < 0: - raise ValueError("Input keyword variance must be zero or positive.") + raise GalSimRangeError("Specified variance must be zero or positive.", + variance, 0, None) # If x_interpolant not specified on input, use bilinear if x_interpolant is None: @@ -1473,7 +1474,8 @@ def __init__(self, variance, rng=None, scale=None, wcs=None, gsparams=None): from .box import Pixel from .convolve import AutoConvolve if variance < 0: - raise ValueError("Input keyword variance must be zero or positive.") + raise GalSimRangeError("Specified variance must be zero or positive.", + variance, 0, None) if wcs is not None: if scale is not None: diff --git a/galsim/detectors.py b/galsim/detectors.py index 2dbc308db33..14ab38238b8 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -24,7 +24,7 @@ import sys from .image import Image -from .errors import GalSimWarning +from .errors import GalSimRangeError, GalSimWarning def applyNonlinearity(self, NLfunc, *args): """ @@ -133,12 +133,15 @@ def addReciprocityFailure(self, exp_time, alpha, base_flux): value. """ - if not isinstance(alpha, float) or alpha < 0.: - raise ValueError("Invalid value of alpha, must be float >= 0") - if not (isinstance(exp_time, float) or isinstance(exp_time, int)) or exp_time < 0.: - raise ValueError("Invalid value of exp_time, must be float or int >= 0") - if not (isinstance(base_flux, float) or isinstance(base_flux,int)) or base_flux < 0.: - raise ValueError("Invalid value of base_flux, must be float or int >= 0") + if alpha < 0.: + raise GalSimRangeError("Invalid value of alpha, must be >= 0", + alpha, 0, None) + if exp_time < 0.: + raise GalSimRangeError("Invalid value of exp_time, must be >= 0", + exp_time, 0, None) + if base_flux < 0.: + raise GalSimRangeError("Invalid value of base_flux, must be >= 0", + base_flux, 0, None) if np.any(self.array<0): import warnings diff --git a/galsim/errors.py b/galsim/errors.py index 047b700c8dc..f07d462a698 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -19,16 +19,28 @@ # Define the class hierarchy for errors and warnings emitted by GalSim that aren't # obviously one of the standard python errors. +from builtins import super + class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. """ - pass -class GalSimRangeError(GalSimError): +class GalSimRangeError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input value is outside of the allowed range of values. + + Attrubutes: + + value = the invalid value + min = the minimum allowed value (may be None) + max = the maximum allowed value (may be None) """ - pass + def __init__(self, message, value, min, max=None): + super().__init__(message + " Value {0!s} not in range [{1!s}, {2!s}].".format( + value, min, max)) + self.value = value + self.min = min + self.max = max class GalSimWarning(UserWarning): diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 4716e2404eb..195e63decd3 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -58,7 +58,7 @@ from . import _galsim from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimWarning class GSObject(object): @@ -1490,11 +1490,11 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N # Make sure (gain, area, exptime) have valid values: if gain <= 0.: - raise ValueError("Invalid gain <= 0.") + raise GalSimRangeError("Invalid gain <= 0.", gain, 0., None) if area <= 0.: - raise ValueError("Invalid area <= 0.") + raise GalSimRangeError("Invalid area <= 0.", area, 0., None) if exptime <= 0.: - raise ValueError("Invalid exptime <= 0.") + raise GalSimRangeError("Invalid exptime <= 0.", exptime, 0., None) if method not in ['auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb']: raise ValueError("Invalid method name = %s"%method) @@ -2042,7 +2042,7 @@ def drawPhot(self, image, gain=1., add_to_image=False, from .image import ImageD # Make sure the type of n_photons is correct and has a valid value: if n_photons < 0.: - raise ValueError("Invalid n_photons < 0.") + raise GalSimRangeError("Invalid n_photons < 0.", n_photons, 0., None) if poisson_flux is None: if n_photons == 0.: poisson_flux = True diff --git a/galsim/image.py b/galsim/image.py index 7b869f3e35f..875066f1f27 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -1206,7 +1206,7 @@ def getValue(self, x, y): if not self.bounds.isDefined(): raise GalSimError("Attempt to access values of an undefined image") if not self.bounds.includes(x,y): - raise GalSimRangeError("Attempt to access position %s,%s, not in bounds %s"%( + raise GalSimError("Attempt to access position %s,%s, not in bounds %s"%( x,y,self.bounds)) return self._getValue(x,y) @@ -1231,7 +1231,7 @@ def setValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimRangeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._setValue(pos.x,pos.y,value) def _setValue(self, x, y, value): @@ -1255,7 +1255,7 @@ def addValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimRangeError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) self._addValue(pos.x,pos.y,value) def _addValue(self, x, y, value): diff --git a/galsim/inclined.py b/galsim/inclined.py index 22b7b186d53..94c7f42df11 100644 --- a/galsim/inclined.py +++ b/galsim/inclined.py @@ -25,6 +25,7 @@ from .exponential import Exponential from .angle import Angle from .position import PositionD +from .errors import GalSimRangeError class InclinedExponential(GSObject): @@ -100,7 +101,7 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale # Check that the scale/half-light radius is valid if scale_radius is not None: if not scale_radius > 0.: - raise ValueError("scale_radius must be > zero.") + raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) if half_light_radius is not None: raise TypeError( "Only one of scale_radius and half_light_radius may be " + @@ -108,7 +109,7 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale self._r0 = float(scale_radius) elif half_light_radius is not None: if not half_light_radius > 0.: - raise ValueError("half_light_radius must be > zero.") + raise GalSimRangeError("half_light_radius must be > 0.", half_light_radius, 0.) # Use the factor from the Exponential class self._r0 = float(half_light_radius) / Exponential._hlr_factor else: @@ -119,7 +120,7 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale # Check that the height specification is valid if scale_height is not None: if not scale_height > 0.: - raise ValueError("scale_height must be > zero.") + raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) if scale_h_over_r is not None: raise TypeError( "Only one of scale_height and scale_h_over_r may be " + @@ -130,7 +131,7 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale # Use the default scale_h_over_r scale_h_over_r = 0.1 elif not scale_h_over_r > 0.: - raise ValueError("half_light_radius must be > zero.") + raise GalSimRangeError("half_light_radius must be > 0.", scale_h_over_r, 0.) self._h0 = float(self._r0) * float(scale_h_over_r) # Explicitly check for angle type, so we can give more informative error if eg. a float is @@ -307,12 +308,12 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc # Check that trunc is valid if trunc < 0.: - raise ValueError("trunc must be >= zero (zero implying no truncation).") + raise GalSimRangeError("trunc must be >= 0. (zero implying no truncation).", trunc, 0.) # Parse the radius options if scale_radius is not None: if not scale_radius > 0.: - raise ValueError("scale_radius must be > zero.") + raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) if half_light_radius is not None: raise TypeError( "Only one of scale_radius and half_light_radius may be " + @@ -321,13 +322,13 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc self._hlr = 0. elif half_light_radius is not None: if not half_light_radius > 0.: - raise ValueError("half_light_radius must be > zero.") + raise GalSimRangeError("half_light_radius must be > 0.", half_light_radius, 0.) self._hlr = float(half_light_radius) if self._trunc == 0. or flux_untruncated: self._r0 = self._hlr / _galsim.SersicHLR(self._n, 1.) else: if self._trunc <= math.sqrt(2.) * self._hlr: - raise ValueError("Sersic trunc must be > sqrt(2) * half_light_radius") + raise GalSimRangerror("Sersic trunc must be > sqrt(2) * half_light_radius") self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) else: raise TypeError( @@ -337,7 +338,7 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc # Parse the height options if scale_height is not None: if not scale_height > 0.: - raise ValueError("scale_height must be > zero.") + raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) if scale_h_over_r is not None: raise TypeError( "Only one of scale_height and scale_h_over_r may be " + @@ -347,7 +348,7 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc if scale_h_over_r is None: scale_h_over_r = 0.1 elif not scale_h_over_r > 0.: - raise ValueError("half_light_radius must be > zero.") + raise GalSimRangeError("half_light_radius must be > 0.", scale_h_over_r, 0.) self._h0 = float(scale_h_over_r) * self._r0 # Explicitly check for angle type, so we can give more informative error if eg. a float is diff --git a/galsim/integ.py b/galsim/integ.py index 228ea3ff927..1a79823783f 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -24,7 +24,7 @@ from functools import reduce from . import _galsim -from .errors import GalSimError +from .errors import GalSimError, GalSimRangeError def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): """Integrate a 1-dimensional function from min to max. @@ -95,7 +95,7 @@ def trapz(func, min, max, points=10000): """ if not np.isscalar(points): if (np.max(points) > max) or (np.min(points) < min): - raise ValueError("Points outside of range: %s -- %s"%(min,max)) + raise GalSimRangeError("Points outside of specified range", points, min, max) elif int(points) != points: raise TypeError("'npoints' must be integer type or array") else: diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 17b8f3ad639..11ef2380c88 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -34,7 +34,7 @@ from .random import BaseDeviate from . import _galsim from . import fits -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimWarning class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -400,7 +400,7 @@ def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, raise ValueError("Supplied pad_image is not one of the allowed types!") if pad_factor <= 0.: - raise ValueError("Invalid pad_factor <= 0 in InterpolatedImage") + raise GalSimRangeError("Invalid pad_factor <= 0 in InterpolatedImage", pad_factor, 0.) # Convert noise_pad_size from arcsec to pixels according to the local wcs. # Use the minimum scale, since we want to make sure noise_pad_size is @@ -465,9 +465,6 @@ def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): # Figure out what kind of noise to apply to the image try: noise_pad = float(noise_pad) - if noise_pad < 0.: - raise ValueError("Noise variance cannot be negative!") - noise = GaussianNoise(rng1, sigma = np.sqrt(noise_pad)) except (TypeError, ValueError): if isinstance(noise_pad, _BaseCorrelatedNoise): @@ -489,6 +486,11 @@ def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename "+ "containing an image to use to make a CorrelatedNoise!") + else: + if noise_pad < 0.: + raise GalSimRangeError("Noise variance may not be negative.", noise_pad, 0.) + noise = GaussianNoise(rng1, sigma = np.sqrt(noise_pad)) + # Find the portion of xim to fill with noise. # It's allowed for the noise padding to not cover the whole pad image half_size = noise_pad_size // 2 diff --git a/galsim/moffat.py b/galsim/moffat.py index 2cbab6bec35..65caff2a9c8 100644 --- a/galsim/moffat.py +++ b/galsim/moffat.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError +from .errors import GalSimError, GalSimRangeError class Moffat(GSObject): @@ -88,10 +88,10 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t self._gsparams = GSParams.check(gsparams) if self._trunc == 0. and self._beta <= 1.1: - raise GalSimRangeError("Moffat profiles with beta <= 1.1 must be truncated") + raise GalSimError("Moffat profiles with beta <= 1.1 must be truncated") if self._trunc < 0.: - raise ValueError("Moffat trunc must be positive") + raise GalSimRangeError("Moffat trunc must be >= 0", self._trunc, 0.) # Parse the radius options if half_light_radius is not None: @@ -101,7 +101,8 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t "specified for Moffat") self._hlr = float(half_light_radius) if self._trunc > 0. and self._trunc <= math.sqrt(2.) * self._hlr: - raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.") + raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.", + self._trunc, math.sqrt(2.) * self._hlr) self._r0 = _galsim.MoffatCalculateSRFromHLR(self._hlr, self._trunc, self._beta) self._fwhm = 0. elif fwhm is not None: diff --git a/galsim/nfw_halo.py b/galsim/nfw_halo.py index a4b5e338b49..0704aeb34a4 100644 --- a/galsim/nfw_halo.py +++ b/galsim/nfw_halo.py @@ -24,6 +24,7 @@ from .angle import arcsec from . import integ from . import utilities +from .errors import GalSimRangeError class Cosmology(object): """Basic cosmology calculations. @@ -90,9 +91,9 @@ def Da(self, z, z_ref=0): return da else: if z < 0: - raise ValueError("Redshift z must not be negative") + raise GalSimRangeError("Redshift z must be >= 0", z, 0.) if z < z_ref: - raise ValueError("Redshift z must not be smaller than the reference redshift") + raise GalSimRangeError("Redshift z must be >= the reference redshift", z, z_ref) d = integ.int1d(self.__angKernel, z_ref+1, z+1) # check for curvature diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index eea2ea910a2..0a94782ea7e 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -220,9 +220,8 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, self._obscuration = obscuration # We store this, even though it's not always used. self._gsparams = gsparams - if obscuration >= 1.: - raise ValueError("Pupil fully obscured! obscuration = {:0} (>= 1)" - .format(obscuration)) + if obscuration < 0. or obscuration >= 1.: + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) # You can either set geometric properties, or use a pupil image, but not both, so check for # that here. One caveat is that we allow sanity checking the sampling of a pupil_image by diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 9f5370ecdf0..7153f491846 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -26,7 +26,7 @@ from . import utilities from . import fft from . import zernike -from .errors import GalSimWarning +from .errors import GalSimRangeError, GalSimWarning class AtmosphericScreen(object): @@ -273,7 +273,7 @@ def _seek(self, t): # Can't reverse, so reset and move forward. if t < self._time: if t < 0.0: - raise ValueError("Can't rewind irreversible screen to t < 0.0") + raise GalSimRangeError("Can't rewind irreversible screen to t < 0.0", t, 0.) self._reset() # Find number of boiling updates we need to perform. previous_update_number = int(self._time // self.time_step) diff --git a/galsim/photon_array.py b/galsim/photon_array.py index baf72b2ac85..4bd6142e53a 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -25,7 +25,7 @@ from . import _galsim from .random import UniformDeviate from .angle import radians, arcsec -from .errors import GalSimError +from .errors import GalSimError, GalSimRangeError # Add on more methods in the python layer @@ -302,7 +302,7 @@ def makeFromImage(cls, image, max_flux=1., rng=None): ud = UniformDeviate(rng) max_flux = float(max_flux) if (max_flux <= 0): - raise ValueError("max_flux must be positive") + raise GalSimRangeError("max_flux must be positive", max_flux, 0.) total_flux = image.array.sum(dtype=float) # This goes a bit over what we actually need, but not by much. Worth it to not have to @@ -428,9 +428,9 @@ class FRatioAngles(object): def __init__(self, fratio, obscuration=0.0, rng=None): if fratio < 0: - raise ValueError("The f-ratio must be positive.") + raise GalSimRangeError("The f-ratio must be positive.", fratio, 0.) if obscuration < 0 or obscuration >= 1: - raise ValueError("The obscuration fraction must be between 0 and 1.") + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) ud = UniformDeviate(rng) self.fratio = fratio diff --git a/galsim/random.py b/galsim/random.py index 45812d2df51..9b66314557c 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -24,6 +24,7 @@ import weakref from . import _galsim +from .errors import GalSimRangeError class BaseDeviate(object): """Base class for all the various random deviates. @@ -299,7 +300,7 @@ class GaussianDeviate(BaseDeviate): """ def __init__(self, seed=None, mean=0., sigma=1.): if sigma < 0.: - raise ValueError("GaussianDeviate sigma must be > 0.") + raise GalSimRangeError("GaussianDeviate sigma must be > 0.", sigma, 0.) self._rng_type = _galsim.GaussianDeviateImpl self._rng_args = (float(mean), float(sigma)) self.reset(seed) @@ -774,8 +775,7 @@ def val(self, p): @returns the corresponding x such that p = cdf(x). """ if p<0 or p>1: - raise ValueError('Cannot request cumulative probability value from DistDeviate for ' - 'p<0 or p>1! You entered: %f'%p) + raise GalSimRangeError('Invalid cumulative probability for DistDeviate', p, 0., 1.) return self._inverse_cdf(p) def __call__(self): diff --git a/galsim/randwalk.py b/galsim/randwalk.py index c0ea31afe07..ccb4853cc63 100644 --- a/galsim/randwalk.py +++ b/galsim/randwalk.py @@ -23,6 +23,7 @@ from .gsobject import GSObject from .position import PositionD from .utilities import lazy_property, doc_inherit +from .errors import GalSimRangeError class RandomWalk(GSObject): """ @@ -199,13 +200,12 @@ def _verify(self): "got %s" % str(self._rng)) if self._npoints <= 0: - raise ValueError("npoints must be > 0, got %s" % str(self._npoints)) + raise GalSimRangeError("npoints must be > 0", self._npoints, 1) if self._half_light_radius <= 0.0: - raise ValueError("half light radius must be > 0" - ", got %s" % str(self._half_light_radius)) + raise GalSimRangeError("half light radius must be > 0", self._half_light_radius, 0.) if self._flux < 0.0: - raise ValueError("flux must be >= 0, got %s" % str(self._flux)) + raise GalSimRangeError("flux must be >= 0", self._flux, 0.) def __str__(self): rep='galsim.RandomWalk(%(npoints)d, %(hlr)g, flux=%(flux)g, gsparams=%(gsparams)s)' diff --git a/galsim/sed.py b/galsim/sed.py index 77c55867ca8..6c3524dce9c 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -31,7 +31,7 @@ from . import integ from . import dcr from .utilities import WeakMethod, lazy_property, combine_wave_list -from .errors import GalSimError +from .errors import GalSimError, GalSimRangeError class SED(object): """Object to represent the spectral energy distributions of stars and galaxies. @@ -369,11 +369,11 @@ def _check_bounds(self, wave): extrapolation_slop = 1.e-6 # allow a small amount of extrapolation if wmin < self.blue_limit - extrapolation_slop: - raise ValueError("Requested wavelength ({0}) is bluer than blue_limit ({1})" - .format(wmin, self.blue_limit)) + raise GalSimRangeError("Requested wavelength is bluer than blue_limit.", + wave, self.blue_limit, self.red_limit) if wmax > self.red_limit + extrapolation_slop: - raise ValueError("Requested wavelength ({0}) is redder than red_limit ({1})" - .format(wmax, self.red_limit)) + raise GalSimRangeError("Requested wavelength is redder than red_limit.", + wave, self.blue_limit, self.red_limit) @lazy_property def _fast_spec(self): @@ -715,7 +715,7 @@ def atRedshift(self, redshift): @returns the redshifted SED. """ if redshift <= -1: - raise ValueError("Invalid redshift {0}".format(redshift)) + raise GalSimRangeError("Invalid redshift", redshift, -1.) zfactor = (1.0 + redshift) / (1.0 + self.redshift) wave_list = self.wave_list * zfactor blue_limit = self.blue_limit * zfactor diff --git a/galsim/sersic.py b/galsim/sersic.py index d88e89a8dd5..c0c6ae900ff 100644 --- a/galsim/sersic.py +++ b/galsim/sersic.py @@ -210,9 +210,11 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, self._gsparams = GSParams.check(gsparams) if self._n < Sersic._minimum_n: - raise GalSimRangeError("Requested Sersic index, %s, is too small"%self._n) + raise GalSimRangeError("Requested Sersic index is too small", + self._n, Sersic._minimum_n, Sersic._maximum_n) if self._n > Sersic._maximum_n: - raise GalSimRangeError("Requested Sersic index, %s, is too large"%self._n) + raise GalSimRangeError("Requested Sersic index is too large", + self._n, Sersic._minimum_n, Sersic._maximum_n) # Parse the radius options if half_light_radius is not None: @@ -226,7 +228,8 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, self._r0 = self._hlr / self.calculateHLRFactor() else: if self._trunc <= math.sqrt(2.) * self._hlr: - raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius") + raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", + self._trunc, math.sqrt(2.) * self._hlr) self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) elif scale_radius is not None: self._r0 = float(scale_radius) diff --git a/galsim/shear.py b/galsim/shear.py index dd7d4001071..b9b6970649d 100644 --- a/galsim/shear.py +++ b/galsim/shear.py @@ -115,7 +115,7 @@ def __init__(self, *args, **kwargs): g2 = kwargs.pop('g2', 0.) self._g = g1 + 1j * g2 if abs(self._g) > 1.: - raise GalSimRangeError("Requested shear exceeds 1: %f"%abs(self._g)) + raise GalSimRangeError("Requested shear exceeds 1.", self._g, 0., 1.) # e1,e2 elif 'e1' in kwargs or 'e2' in kwargs: @@ -123,7 +123,7 @@ def __init__(self, *args, **kwargs): e2 = kwargs.pop('e2', 0.) absesq = e1**2 + e2**2 if absesq > 1.: - raise GalSimRangeError("Requested distortion exceeds 1: %s"%np.sqrt(absesq)) + raise GalSimRangeError("Requested distortion exceeds 1.",np.sqrt(absesq), 0., 1.) self._g = (e1 + 1j * e2) * self._e2g(absesq) # eta1,eta2 @@ -145,7 +145,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") g = kwargs.pop('g') if g > 1 or g < 0: - raise GalSimRangeError("Requested |shear| is outside [0,1]: %f"%g) + raise GalSimRangeError("Requested |shear| is outside [0,1].",g, 0., 1.) self._g = g * np.exp(2j * beta.rad) # e,beta @@ -159,7 +159,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") e = kwargs.pop('e') if e > 1 or e < 0: - raise GalSimRangeError("Requested distortion is outside [0,1]: %f"%e) + raise GalSimRangeError("Requested distortion is outside [0,1].", e, 0., 1.) self._g = self._e2g(e**2) * e * np.exp(2j * beta.rad) # eta,beta @@ -173,7 +173,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") eta = kwargs.pop('eta') if eta < 0: - raise GalSimRangeError("Requested eta is below 0: %f"%eta) + raise GalSimRangeError("Requested eta is below 0.", eta, 0.) self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) # q,beta @@ -187,7 +187,7 @@ def __init__(self, *args, **kwargs): "The position angle that was supplied is not an Angle instance!") q = kwargs.pop('q') if q <= 0 or q > 1: - raise GalSimRangeError("Cannot use requested axis ratio of %f!"%q) + raise GalSimRangeError("Cannot use requested axis ratio.", q, 0., 1.) eta = -np.log(q) self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) diff --git a/galsim/spergel.py b/galsim/spergel.py index f06eead30aa..0b7c87d1575 100644 --- a/galsim/spergel.py +++ b/galsim/spergel.py @@ -118,9 +118,11 @@ def __init__(self, nu, half_light_radius=None, scale_radius=None, self._gsparams = GSParams.check(gsparams) if self._nu < Spergel._minimum_nu: - raise GalSimRangeError("Requested Spergel index, %s, is too small"%self._nu) + raise GalSimRangeError("Requested Spergel index is too small", + self._nu, Spergel._minimum_nu, Spergel._maximum_nu) if self._nu > Spergel._maximum_nu: - raise GalSimRangeError("Requested Spergel index, %s, is too large"%self._nu) + raise GalSimRangeError("Requested Spergel index is too large", + self._nu, Spergel._minimum_nu, Spergel._maximum_nu) # Parse the radius options if half_light_radius is not None: diff --git a/galsim/table.py b/galsim/table.py index 09da51ef79c..744da0d1a83 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -26,6 +26,7 @@ from . import _galsim from .utilities import lazy_property +from .errors import GalSimRangeError class LookupTable(object): """ @@ -193,11 +194,11 @@ def __call__(self, x): def _check_range(self, x): slop = (self.x_max - self.x_min) * 1.e-6 if np.min(x) < self.x_min - slop: - raise ValueError("x value(s) below the range of the LookupTable: %s < %s"%( - x, self.x_min)) + raise GalSimRangeError("x value(s) below the range of the LookupTable.", + x, self.x_min, self.x_max) if np.max(x) > self.x_max + slop: - raise ValueError("x value(s) above the range of the LookupTable: %s > %s"%( - x, self.x_max)) + raise GalSimRangeError("x value(s) above the range of the LookupTable.", + x, self.x_min, self.x_max) def getArgs(self): return self.x diff --git a/galsim/utilities.py b/galsim/utilities.py index 4355a8b198e..57af8304b2b 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -393,7 +393,7 @@ def thin_tabulated_values(x, f, rel_err=1.e-4, trim_zeros=True, preserve_range=T if len(x) != len(f): raise ValueError("len(x) != len(f)") if rel_err <= 0 or rel_err >= 1: - raise ValueError("rel_err must be between 0 and 1") + raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): raise ValueError("input x is not sorted.") @@ -491,7 +491,7 @@ def old_thin_tabulated_values(x, f, rel_err=1.e-4, preserve_range=False): # prag if len(x) != len(f): raise ValueError("len(x) != len(f)") if rel_err <= 0 or rel_err >= 1: - raise ValueError("rel_err must be between 0 and 1") + raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): raise ValueError("input x is not sorted.") From 36e90a82fab1e7a41bba9e3e46bae6012cba86bd Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 11:13:36 -0400 Subject: [PATCH 06/96] Use GalSimBoundsError for when positions fall outside allowed bounds (#755) --- galsim/errors.py | 15 +++++++++++++++ galsim/image.py | 12 +++++++----- galsim/table.py | 14 +++++++++++--- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/galsim/errors.py b/galsim/errors.py index f07d462a698..a8d4d0345da 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -42,6 +42,21 @@ def __init__(self, message, value, min, max=None): self.min = min self.max = max +class GalSimBoundsError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that some user-input position is + outside of the allowed bounds. + + Attrubutes: + + pos = the invalid position + bounds = the bounds in which it was expected to fall + """ + def __init__(self, message, pos, bounds): + super().__init__(message + " Position {0!s} not in bounds {1!s}.".format( + pos, bounds)) + self.pos = pos + self.bounds = bounds + class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. diff --git a/galsim/image.py b/galsim/image.py index 875066f1f27..70d28519435 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -27,7 +27,7 @@ from .bounds import BoundsI, BoundsD from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities -from .errors import GalSimError, GalSimRangeError +from .errors import GalSimError, GalSimBoundsError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -1206,8 +1206,8 @@ def getValue(self, x, y): if not self.bounds.isDefined(): raise GalSimError("Attempt to access values of an undefined image") if not self.bounds.includes(x,y): - raise GalSimError("Attempt to access position %s,%s, not in bounds %s"%( - x,y,self.bounds)) + raise GalSimBoundsError("Attempt to access position not in bounds of image.", + PositionI(x,y), self.bounds) return self._getValue(x,y) def _getValue(self, x, y): @@ -1231,7 +1231,8 @@ def setValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimBoundsError("Attempt to set position not in bounds of image", + pos, self.bounds) self._setValue(pos.x,pos.y,value) def _setValue(self, x, y, value): @@ -1255,7 +1256,8 @@ def addValue(self, *args, **kwargs): pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): - raise GalSimError("Attempt to set position %s, not in bounds %s"%(pos,self.bounds)) + raise GalSimBoundsError("Attempt to set position not in bounds of image", + pos,self.bounds) self._addValue(pos.x,pos.y,value) def _addValue(self, x, y, value): diff --git a/galsim/table.py b/galsim/table.py index 744da0d1a83..977af4d9de9 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -26,7 +26,9 @@ from . import _galsim from .utilities import lazy_property -from .errors import GalSimRangeError +from .position import PositionD +from .bounds import BoundsD +from .errors import GalSimRangeError, GalSimBoundsError class LookupTable(object): """ @@ -481,9 +483,14 @@ def _wrap_args(self, x, y): return ((x-self.x[0]) % self.xperiod + self.x[0], (y-self.y[0]) % self.yperiod + self.y[0]) + @property + def _bounds(self): + return BoundsD(self.x[0], self.x[-1], self.y[0], self.y[-1]) + def _call_raise(self, x, y): if not self._inbounds(x, y): - raise ValueError("Extrapolating beyond input range.") + raise GalSimBoundsError("Extrapolating beyond input range.", + PositionD(x,y), self._bounds) if isinstance(x, numbers.Real): return self._tab.interp(x, y) @@ -529,7 +536,8 @@ def __call__(self, x, y): def _gradient_raise(self, x, y): if not self._inbounds(x, y): - raise ValueError("Extrapolating beyond input range.") + raise GalSimBoundsError("Extrapolating beyond input range.", + PositionD(x,y), self._bounds) if isinstance(x, numbers.Real): grad = np.empty(2, dtype=float) From 41b4d1009e9af8bbd89d052c93e01301a932d946 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 14:19:33 -0400 Subject: [PATCH 07/96] Use GalSimValueError for invalid parameter values (#755) --- galsim/__init__.py | 5 ++- galsim/bandpass.py | 29 ++++++++--------- galsim/bounds.py | 8 ++--- galsim/catalog.py | 35 +++++++++++---------- galsim/cdmodel.py | 12 +++---- galsim/chromatic.py | 8 ++--- galsim/convolve.py | 6 ++-- galsim/correlatednoise.py | 12 +++---- galsim/dcr.py | 3 +- galsim/detectors.py | 55 ++++++++++----------------------- galsim/errors.py | 34 +++++++++++++++++--- galsim/fft.py | 17 +++++----- galsim/fits.py | 48 +++++++++++++++------------- galsim/gsobject.py | 21 +++++++------ galsim/hsm.py | 7 ++--- galsim/image.py | 38 +++++++++++------------ galsim/integ.py | 9 +++--- galsim/interpolant.py | 9 ++++-- galsim/interpolatedimage.py | 26 +++++++++------- galsim/lensing_ps.py | 38 +++++++++++------------ galsim/main.py | 10 +++--- galsim/phase_psf.py | 10 +++--- galsim/phase_screens.py | 4 +-- galsim/position.py | 12 +++---- galsim/pse.py | 8 ++--- galsim/random.py | 11 +++---- galsim/real.py | 31 +++++++++---------- galsim/scene.py | 15 ++++----- galsim/sed.py | 27 +++++++++------- galsim/sensor.py | 4 +-- galsim/shapelet.py | 6 ++-- galsim/table.py | 40 ++++++++++++++---------- galsim/utilities.py | 18 ++++++----- galsim/zernike.py | 5 +-- tests/test_chromatic.py | 2 +- tests/test_detectors.py | 2 +- tests/test_image.py | 4 +-- tests/test_interpolatedimage.py | 2 +- tests/test_lensing.py | 12 +++---- tests/test_pse.py | 4 +-- tests/test_real.py | 8 ++--- tests/test_utilities.py | 30 +++++++++--------- 42 files changed, 359 insertions(+), 326 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index 90af8f12c76..f8b0159ac92 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -100,7 +100,10 @@ from .catalog import Catalog, Dict, OutputCatalog from .scene import COSMOSCatalog from .table import LookupTable, LookupTable2D -from .errors import GalSimError, GalSimRangeError, GalSimWarning + +# Exception and Warning classes +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimHSMError +from .errors import GalSimWarning # Image from .image import Image, ImageS, ImageI, ImageF, ImageD, ImageCF, ImageCD, ImageUS, ImageUI, _Image diff --git a/galsim/bandpass.py b/galsim/bandpass.py index 12656a0f7f1..c122b5978a6 100644 --- a/galsim/bandpass.py +++ b/galsim/bandpass.py @@ -29,7 +29,7 @@ from . import integ from . import meta_data from .utilities import WeakMethod, combine_wave_list -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimValueError class Bandpass(object): """Simple bandpass object, which models the transmission fraction of incident light as a @@ -120,14 +120,14 @@ def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, # Parse the various options for wave_type if isinstance(wave_type, str): - if wave_type.lower() in ['nm', 'nanometer', 'nanometers']: + if wave_type.lower() in ('nm', 'nanometer', 'nanometers'): self.wave_type = 'nm' self.wave_factor = 1. - elif wave_type.lower() in ['a', 'ang', 'angstrom', 'angstroms']: + elif wave_type.lower() in ('a', 'ang', 'angstrom', 'angstroms'): self.wave_type = 'Angstrom' self.wave_factor = 10. else: - raise ValueError("Unknown wave_type '{0}'".format(wave_type)) + raise GalSimValueError("Invalid wave_type.", wave_type, ('nm', 'Angstrom')) else: self.wave_type = wave_type try: @@ -139,7 +139,7 @@ def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, self.wave_factor = 10. except units.UnitConversionError: # Unlike in SED, we require a distance unit for wave_type - raise ValueError("Unknown wave_type '{0}'".format(wave_type)) + raise GalSimValueError("Invalid wave_type. Must be a distance.", wave_type) # Convert string input into a real function (possibly a LookupTable) self._initialize_tp() @@ -226,16 +226,16 @@ def _initialize_tp(self): self._tp = utilities.math_eval('lambda wave : ' + self._orig_tp) test_value = self._tp(test_wave) except Exception as e: - raise ValueError( + raise GalSimValueError( "String throughput must either be a valid filename or something that "+ - "can eval to a function of wave.\n" + - "Input provided: {0!r}\n".format(self._orig_tp) + - "Caught error: {0}".format(e)) + "can eval to a function of wave.\n Caught error: %s."%(e), + self._orig_tp) from numbers import Real if not isinstance(test_value, Real): - raise ValueError("The given throughput function, %r, did not return a valid" - " number at test wavelength %s: got %s"%( - self._orig_tp, test_wave, test_value)) + raise GalSimValueError( + "The given throughput function did not return a valid " + "number at test wavelength %s: got %s."%(test_wave, test_value), + self._orig_tp) else: self._tp = self._orig_tp @@ -400,7 +400,8 @@ def withZeropoint(self, zeropoint): vegafile = os.path.join(meta_data.share_dir, "SEDs", "vega.txt") sed = SED(vegafile, wave_type='nm', flux_type='flambda') else: - raise ValueError("Unrecognized Zeropoint string {0}.".format(zeropoint)) + raise GalSimValueError("Unrecognized Zeropoint string.", zeropoint, + ('AB', 'ST', 'VEGA')) zeropoint = sed # Convert `zeropoint` from galsim.SED to float @@ -471,7 +472,7 @@ def truncate(self, blue_limit=None, red_limit=None, relative_throughput=None, else: preserve_zp = False # Check for weird input if preserve_zp is not True and preserve_zp is not False: - raise ValueError("Unrecognized input for preserve_zp: %s"%preserve_zp) + raise GalSimValueError("Unrecognized input for preserve_zp.",preserve_zp) if blue_limit is None: blue_limit = self.blue_limit diff --git a/galsim/bounds.py b/galsim/bounds.py index 44a6e5060c2..bdcfedc337d 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -374,7 +374,7 @@ def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) if (self.xmin != float(self.xmin) or self.xmax != float(self.xmax) or self.ymin != float(self.ymin) or self.ymax != float(self.ymax)): - raise ValueError("BoundsD must be initialized with float values") + raise TypeError("BoundsD must be initialized with float values") self.xmin = float(self.xmin) self.xmax = float(self.xmax) self.ymin = float(self.ymin) @@ -392,7 +392,7 @@ def _check_scalar(self, x, name): if x == float(x): return except (TypeError, ValueError): pass - raise ValueError("%s must be a float value"%name) + raise TypeError("%s must be a float value"%name) def _area(self): return (self.xmax - self.xmin) * (self.ymax - self.ymin) @@ -415,7 +415,7 @@ def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) if (self.xmin != int(self.xmin) or self.xmax != int(self.xmax) or self.ymin != int(self.ymin) or self.ymax != int(self.ymax)): - raise ValueError("BoundsI must be initialized with integer values") + raise TypeError("BoundsI must be initialized with integer values") # Now make sure they are all ints self.xmin = int(self.xmin) self.xmax = int(self.xmax) @@ -431,7 +431,7 @@ def _check_scalar(self, x, name): if x == int(x): return except (TypeError, ValueError): pass - raise ValueError("%s must be a integer value"%name) + raise TypeError("%s must be a integer value"%name) def numpyShape(self): "A simple utility function to get the numpy shape that corresponds to this Bounds object." diff --git a/galsim/catalog.py b/galsim/catalog.py index 2ed49538279..2a2c07a0470 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -24,6 +24,8 @@ import os import numpy as np +from .errors import GalSimValueError + class Catalog(object): """A class storing the data from an input catalog. @@ -78,18 +80,17 @@ def __init__(self, file_name, dir=None, file_type=None, comments='#', hdu=1, else: file_type = 'ASCII' file_type = file_type.upper() - if file_type not in ['FITS', 'ASCII']: - raise ValueError("file_type must be either FITS or ASCII if specified.") + if file_type not in ('FITS', 'ASCII'): + raise GalSimValueError("file_type must be either FITS or ASCII if specified.", + file_type, ('FITS', 'ASCII')) self.file_type = file_type self.comments = comments self.hdu = hdu if file_type == 'FITS': self.readFits(hdu, _nobjects_only) - elif file_type == 'ASCII': + else: # file_type == 'ASCII': self.readAscii(comments, _nobjects_only) - else: - raise ValueError("Invalid file_type %s"%file_type) # When we make a proxy of this class (cf. galsim/config/stamp.py), the attributes # don't get proxied. Only callable methods are. So make method versions of these. @@ -263,11 +264,12 @@ def __init__(self, file_name, dir=None, file_type=None, key_split='.'): elif ext.lower().startswith('.j'): file_type = 'JSON' else: - raise ValueError('Unable to determine file_type from file_name ending') + raise GalSimValueError('Unable to determine file_type from file_name ending', + file_name, ('*.p*', '*.y*', '*.j*')) file_type = file_type.upper() - if file_type not in ['PICKLE','YAML','JSON']: - raise ValueError("file_type must be one of Pickle, YAML, or JSON if specified.") + if file_type not in ('PICKLE','YAML','JSON'): + raise GalSimValueError("Invalid file_type", file_type, ('Pickle', 'YAML', 'JSON')) self.file_type = file_type self.key_split = key_split @@ -288,7 +290,7 @@ def __init__(self, file_name, dir=None, file_type=None, key_split='.'): with open(self.file_name, 'r') as f: self.dict = json.load(f) else: - raise ValueError("Invalid file_type %s"%file_type) + raise GalSimValueError("Invalid file_type", file_type, ('Pickle', 'YAML', 'JSON')) def get(self, key, default=None): # Make a list of keys according to our key_split parameter @@ -306,10 +308,10 @@ def get(self, key, default=None): # Otherwise, return the result. else: if k not in d and default is None: - raise ValueError("key=%s not found in dictionary"%key) + raise GalSimValueError("key not found in dictionary.",key) return d.get(k,default) - raise ValueError("Invalid key=%s given to Dict.get()"%key) + raise GalSimValueError("Invalid key given to Dict.get()",key) # The rest of the functions are typical non-mutating functions for a dict, for which we just # pass the request along to self.dict. @@ -423,7 +425,8 @@ def addRow(self, row, sort_key=None): which will be used at the end to re-sort the rows. """ if len(row) != self.ncols: - raise ValueError("Length of row does not match the number of columns") + raise GalSimValueError("Length of row does not match the number of columns = %d"%( + self.ncols), len(row)) self.rows.append(tuple(row)) if sort_key is None: self.sort_keys.append(self.nobjects) @@ -453,15 +456,13 @@ def write(self, file_name, dir=None, file_type=None, prec=8): else: file_type = 'ASCII' file_type = file_type.upper() - if file_type not in ['FITS', 'ASCII']: - raise ValueError("file_type must be either FITS or ASCII if specified.") + if file_type not in ('FITS', 'ASCII'): + raise GalSimValueError("Invalid file_type.", file_type, ('FITS', 'ASCII')) if file_type == 'FITS': self.writeFits(file_name) - elif file_type == 'ASCII': + else: # file_type == 'ASCII': self.writeAscii(file_name, prec) - else: - raise ValueError("Invalid file_type %s"%file_type) def makeData(self): """Returns a numpy array of the data as it should be written to an output file. diff --git a/galsim/cdmodel.py b/galsim/cdmodel.py index 2af43b8f263..f2ca1d59ba7 100644 --- a/galsim/cdmodel.py +++ b/galsim/cdmodel.py @@ -27,6 +27,7 @@ from .image import Image from . import _galsim +from .errors import GalSimValueError class BaseCDModel(object): """Base class for the most generic, i.e. no with symmetries or distance scaling relationships @@ -66,16 +67,16 @@ def __init__(self, a_l, a_r, a_b, a_t): """ # Some basic sanity checking if (a_l.shape[0] % 2 != 1): - raise ValueError("Input array must be odd-dimensioned") + raise GalSimValueError("Input array must be odd-dimensioned", a_l.shape) for a in (a_l, a_r, a_b, a_t): if a.shape[0] != a.shape[1]: - raise ValueError("Input array is not square") + raise GalSimValueError("Input array is not square", a.shape) if a.shape[0] != a_l.shape[0]: - raise ValueError("Input arrays not all the same dimensions") + raise GalSimValueError("Input arrays not all the same dimensions", a.shape) # Save the relevant dimension and the matrices storing deflection coefficients self.n = a_l.shape[0] // 2 if (self.n < 1): - raise ValueError("Input arrays must be at least 3x3") + raise GalSimValueError("Input arrays must be at least 3x3", a_l.shape) self.a_l = Image(a_l, dtype=np.float64, make_const=True) self.a_r = Image(a_r, dtype=np.float64, make_const=True) @@ -205,8 +206,7 @@ def __init__(self, n, r0, t0, rx, tx, r, t, alpha): @param t power-law amplitude for contribution to deflection along y from further away @param alpha power-law exponent for deflection from further away """ - if not isinstance(n, int): - raise ValueError("Input separation n must be an int") + n = int(n) # First define x and y coordinates in a square grid of ints of shape (2n + 1) * (2n + 1) x, y = np.meshgrid(np.arange(2 * n + 1) - n, np.arange(2 * n + 1) - n) diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 2c13349b1fc..2b4f3926ea1 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -213,7 +213,7 @@ def _fiducial_profile(self, bandpass): if prof0.flux != 0: return w, prof0 - raise ValueError("Could not locate fiducial wavelength where SED * Bandpass is nonzero.") + raise GalSimError("Could not locate fiducial wavelength where SED * Bandpass is nonzero.") def __eq__(self, other): return (isinstance(other, ChromaticObject) and @@ -416,7 +416,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # merge self.wave_list into bandpass.wave_list if using a sampling integrator if isinstance(integrator, integ.SampleIntegrator): if len(wave_list) < 2: - raise AttributeError( + raise ValueError( "Cannot use SampleIntegrator when Bandpass and SED are both " "analytic.") bandpass = Bandpass(LookupTable(wave_list, bandpass(wave_list), @@ -1724,7 +1724,7 @@ def __init__(self, *args, **kwargs): if len(args) == 0: # No arguments. Could initialize with an empty list but draw then segfaults. Raise an # exception instead. - raise ValueError("Must provide at least one GSObject or ChromaticObject.") + raise TypeError("Must provide at least one GSObject or ChromaticObject.") elif len(args) == 1: # 1 argument. Should be either a GSObject, ChromaticObject or a list of these. if isinstance(args[0], (GSObject, ChromaticObject)): @@ -1902,7 +1902,7 @@ def __init__(self, *args, **kwargs): if len(args) == 0: # No arguments. Could initialize with an empty list but draw then segfaults. Raise an # exception instead. - raise ValueError("Must provide at least one GSObject or ChromaticObject") + raise TypeError("Must provide at least one GSObject or ChromaticObject") elif len(args) == 1: if isinstance(args[0], (GSObject, ChromaticObject)): args = [args[0]] diff --git a/galsim/convolve.py b/galsim/convolve.py index 45d11807cdf..58bfd615f14 100644 --- a/galsim/convolve.py +++ b/galsim/convolve.py @@ -356,7 +356,8 @@ def _xValue(self, pos): raise NotImplementedError( "At least one profile in %s does not implement real-space convolution"%self) else: - raise ValueError("Cannot use real_space convolution for >2 profiles") + # XXX Not sure if this code is reachable... + raise GalSimError("Cannot use real_space convolution for >2 profiles") @doc_inherit def _kValue(self, pos): @@ -374,7 +375,8 @@ def _drawReal(self, image): raise NotImplementedError( "At least one profile in %s does not implement real-space convolution"%self) else: - raise ValueError("Cannot use real_space convolution for >2 profiles") + # XXX Not sure if this code is reachable... + raise GalSimError("Cannot use real_space convolution for >2 profiles") @doc_inherit def _shoot(self, photons, ud): diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index eb62e2c64a9..455c12a4596 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -26,7 +26,7 @@ from .random import BaseDeviate from .gsobject import GSObject from . import utilities -from .errors import GalSimError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimWarning def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -400,11 +400,11 @@ def symmetrizeImage(self, image, order=4): # Check that the input is square in shape. if image.array.shape[0] != image.array.shape[1]: - raise ValueError("Input image is not square!") + raise GalSimValueError("Input image must be square.", image.array.shape) # Check that the input order is an allowed value. if order % 2 != 0 or order <= 2: - raise ValueError("Order must be an even number >=4!") + raise GalSimValueError("Order must be an even number >=4.", order) # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. Note that this cache is not the same as the one used for @@ -929,7 +929,7 @@ def _generate_noise_from_rootps(rng, shape, rootps): from .random import GaussianDeviate # Sanity check on requested shape versus that of rootps if len(shape) != 2 or (shape[0], shape[1]//2+1) != rootps.shape: - raise ValueError("Requested shape does not match that of the supplied rootps") + raise GalSimValueError("Requested shape does not match that of the supplied rootps", shape) # Quickest to create Gaussian rng each time needed, so do that here... gd = GaussianDeviate( rng, sigma=np.sqrt(.5 * shape[0] * shape[1])) # Note sigma scaling: 1/sqrt(2) needed so @@ -1217,7 +1217,7 @@ def __init__(self, image, rng=None, scale=None, wcs=None, x_interpolant=None, if scale is not None: raise ValueError("Cannot provide both wcs and scale") if not wcs.isUniform(): - raise ValueError("Cannot provide non-uniform wcs") + raise GalSimValueError("Cannot provide non-uniform wcs", wcs) if not isinstance(wcs, BaseWCS): raise TypeError("wcs must be a BaseWCS instance") cf_image.wcs = wcs @@ -1483,7 +1483,7 @@ def __init__(self, variance, rng=None, scale=None, wcs=None, gsparams=None): if not isinstance(wcs, BaseWCS): raise TypeError("wcs must be a BaseWCS instance") if not wcs.isUniform(): - raise ValueError("Cannot provide non-uniform wcs") + raise GalSimValueError("Cannot provide non-uniform wcs", wcs) elif scale is not None: wcs = PixelScale(scale) else: diff --git a/galsim/dcr.py b/galsim/dcr.py index 7d324764b2f..ad737e9a261 100644 --- a/galsim/dcr.py +++ b/galsim/dcr.py @@ -94,8 +94,7 @@ def zenith_parallactic_angles(obj_coord, zenith_coord=None, HA=None, latitude=No from .angle import degrees if zenith_coord is None: if HA is None or latitude is None: - raise ValueError("Need to provide either zenith_coord or (HA, latitude) to" - +"zenith_parallactic_angles()") + raise TypeError("Must provide either zenith_coord or (HA, latitude).") zenith_coord = CelestialCoord(HA + obj_coord.ra, latitude) zenith_angle = obj_coord.distanceTo(zenith_coord) NCP = CelestialCoord(0.0*degrees, 90*degrees) diff --git a/galsim/detectors.py b/galsim/detectors.py index 14ab38238b8..2b2310d2315 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -24,7 +24,7 @@ import sys from .image import Image -from .errors import GalSimRangeError, GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimWarning def applyNonlinearity(self, NLfunc, *args): """ @@ -74,9 +74,10 @@ def applyNonlinearity(self, NLfunc, *args): # Extract out the array from Image since not all functions can act directly on Images result = NLfunc(self.array,*args) if not isinstance(result, np.ndarray): - raise ValueError("NLfunc does not return a NumPy array.") + raise GalSimValueError("NLfunc does not return a NumPy array.", NLfunc) if self.array.shape != result.shape: - raise ValueError("NLfunc does not return a NumPy array of the same shape as input!") + raise GalSimValueError("NLfunc does not return a NumPy array of the same shape as input.", + NLfunc) self.array[:,:] = result @@ -206,17 +207,15 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ @returns None """ - # IPC kernel has to be a 3x3 Image instance - if not isinstance(IPC_kernel, Image): - raise ValueError("IPC_kernel must be an Image instance .") + # IPC kernel has to be a 3x3 Image ipc_kernel = IPC_kernel.array if not ipc_kernel.shape==(3,3): - raise ValueError("IPC kernel must be an Image instance of size 3x3.") + raise GalSimValueError("IPC kernel must be an Image instance of size 3x3.", IPC_kernel) # Check for non-negativity of the kernel if kernel_nonnegativity is True: if (ipc_kernel<0).any() is True: - raise ValueError("IPC kernel must not contain negative entries") + raise GalSimValueError("IPC kernel must not contain negative entries", IPC_kernel) # Check and enforce correct normalization for the kernel if kernel_normalization is True: @@ -244,7 +243,8 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ pad_array[:,0] = pad_array[:,-2] pad_array[:,-1] = pad_array[:,1] else: - raise ValueError("edge_treatment has to be one of 'extend', 'wrap' or 'crop'. ") + raise GalSimValueError("Invalid edge_treatment.", edge_treatment, + ('extend', 'wrap', 'crop')) # Generating different segments of the padded array center = pad_array[1:-1,1:-1] @@ -271,13 +271,10 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ self.array[1:-1,1:-1] = out_array #Explicit edge effects handling with filling the edges with the value given in fill_value if fill_value is not None: - if isinstance(fill_value, float) or isinstance(fill_value, int): - self.array[0,:] = fill_value - self.array[-1,:] = fill_value - self.array[:,0] = fill_value - self.array[:,-1] = fill_value - else: - raise ValueError("'fill_value' must be either a float or an int") + self.array[0,:] = fill_value + self.array[-1,:] = fill_value + self.array[:,0] = fill_value + self.array[:,-1] = fill_value else: self.array[:,:] = out_array @@ -305,33 +302,15 @@ def applyPersistence(self,imgs,coeffs): >>> img.applyPersistence(imgs=img_list, coeffs=coeffs_list) - @ param imgs A list of previous Image instances that still persist. - @ param coeffs A list of floats that specifies the retention factors for the corresponding + @param imgs A list of previous Image instances that still persist. + @param coeffs A list of floats that specifies the retention factors for the corresponding Image instances listed in 'imgs'. - @ returns None + @returns None """ - - if not hasattr(imgs,'__iter__') or not hasattr(coeffs,'__iter__'): - raise TypeError("Type mismatch between 'imgs' and 'coeffs' in 'applyPersistence' routine. " - "'imgs' must be a list of Image instances and 'coeffs' must be a list of " - "floats of the same length.") - if not len(imgs)==len(coeffs): - raise TypeError("The length of 'imgs' and 'coeffs' must be the same, if passed as a " - "list") - # If this error is not raised, then the images are added as long as one of the list is - # exhausted. - + raise ValueError("The length of 'imgs' and 'coeffs' must be the same") for img,coeff in zip(imgs,coeffs): - if not isinstance(img, Image): - raise ValueError("In 'applyPersistence', the objects in 'imgs' must be " - "galsim.Image instances") - - if not isinstance(coeff,float): - raise ValueError("In 'applyPersistence', the objects in 'coeffs' must be " - "of type float") - self += coeff*img def quantize(self): diff --git a/galsim/errors.py b/galsim/errors.py index a8d4d0345da..0d84e59826e 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -24,6 +24,26 @@ class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. """ + pass + + +class GalSimValueError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that some user-input value is invalid. + + Attrubutes: + + value = the invalid value + allowed_values = a list of allowed values if appropriate (may be None) + """ + def __init__(self, message, value, allowed_values=None): + message += " Value {0!s}".format(value) + if allowed_values: + message += " not in {0!s}".format(allowed_values) + super().__init__(message) + self.value = value + self.min = min + self.max = max + class GalSimRangeError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input value is @@ -36,8 +56,8 @@ class GalSimRangeError(GalSimError, ValueError): max = the maximum allowed value (may be None) """ def __init__(self, message, value, min, max=None): - super().__init__(message + " Value {0!s} not in range [{1!s}, {2!s}].".format( - value, min, max)) + message += " Value {0!s} not in range [{1!s}, {2!s}].".format(value, min, max) + super().__init__(message) self.value = value self.min = min self.max = max @@ -52,12 +72,18 @@ class GalSimBoundsError(GalSimError, ValueError): bounds = the bounds in which it was expected to fall """ def __init__(self, message, pos, bounds): - super().__init__(message + " Position {0!s} not in bounds {1!s}.".format( - pos, bounds)) + message += " Position {0!s} not in bounds {1!s}.".format(pos, bounds) + super().__init__(message) self.pos = pos self.bounds = bounds +class GalSimHSMError(GalSimError): + """A GalSim-specific exception class indicating some kind of failure of the HSM algorithms + """ + pass + + class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ diff --git a/galsim/fft.py b/galsim/fft.py index 9c276c2d223..736238df472 100644 --- a/galsim/fft.py +++ b/galsim/fft.py @@ -39,6 +39,7 @@ from . import _galsim from .image import Image, ImageD, ImageCD from .bounds import BoundsI +from .errors import GalSimValueError def fft2(a, shift_in=False, shift_out=False): """Compute the 2-dimensional discrete Fourier Transform. @@ -78,13 +79,13 @@ def fft2(a, shift_in=False, shift_out=False): """ s = a.shape if len(s) != 2: - raise ValueError("Input array must be 2D. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must be 2D.",s) M, N = s Mo2 = M // 2 No2 = N // 2 if M != Mo2*2 or N != No2*2: - raise ValueError("Input array must have even sizes. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must have even sizes.",s) if a.dtype.kind == 'c': a = a.astype(np.complex128, copy=False) @@ -163,13 +164,13 @@ def ifft2(a, shift_in=False, shift_out=False): """ s = a.shape if len(s) != 2: - raise ValueError("Input array must be 2D. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must be 2D.",s) M,N = s Mo2 = M // 2 No2 = N // 2 if M != Mo2*2 or N != No2*2: - raise ValueError("Input array must have even sizes. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must have even sizes.",s) if a.dtype.kind == 'c': a = a.astype(np.complex128, copy=False) @@ -219,13 +220,13 @@ def rfft2(a, shift_in=False, shift_out=False): """ s = a.shape if len(s) != 2: - raise ValueError("Input array must be 2D. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must be 2D.",s) M,N = s Mo2 = M // 2 No2 = N // 2 if M != Mo2*2 or N != No2*2: - raise ValueError("Input array must have even sizes. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must have even sizes.",s) a = a.astype(np.float64, copy=False) xim = ImageD(a, xmin = -No2, ymin = -Mo2) @@ -271,13 +272,13 @@ def irfft2(a, shift_in=False, shift_out=False): """ s = a.shape if len(s) != 2: - raise ValueError("Input array must be 2D. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must be 2D.",s) M,No2 = s No2 -= 1 # s is (M,No2+1) Mo2 = M // 2 if M != Mo2*2: - raise ValueError("Input array must have even sizes. Got shape=%s"%str(s)) + raise GalSimValueError("Input array must have even sizes.",s) a = a.astype(np.complex128, copy=False) kim = ImageCD(a, xmin = 0, ymin = -Mo2) diff --git a/galsim/fits.py b/galsim/fits.py index d8917c7ac63..572ea371278 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -28,7 +28,7 @@ import numpy as np from .image import Image -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning ############################################################################################## @@ -56,7 +56,9 @@ def _parse_compression(compression, file_name): elif file_name.lower().endswith('.bz2'): file_compress = 'bzip2' else: pass else: - raise ValueError("Invalid compression %s"%compression) + raise GalSimValueError("Invalid compression", compression, + ('rice', 'gzip_tile', 'hcompress', 'plio', 'gzip', 'bzip2', + 'none', 'auto')) if pyfits_compress: if 'CompImageHDU' not in pyfits.__dict__: raise NotImplementedError( @@ -218,7 +220,8 @@ def __call__(self, file, dir, file_compress): except: # pragma: no cover self.gz_index += 1 self.gz = self.gz_methods[self.gz_index] - raise GalSimError("None of the options for gunzipping were successful.") + else: # pragma: no cover + raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': with open(file) as fid: pass while self.bz2_index < len(self.bz2_methods): @@ -229,9 +232,10 @@ def __call__(self, file, dir, file_compress): except: # pragma: no cover self.bz2_index += 1 self.bz2 = self.bz2_methods[self.bz2_index] - raise GalSimError("None of the options for bunzipping were successful.") + else: # pragma: no cover + raise GalSimError("None of the options for bunzipping were successful.") else: - raise ValueError("Unknown file_compression") + raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) _read_file = _ReadFile() # Do the same trick for _write_file(file,hdu_list,clobber,file_compress,pyfits_compress): @@ -395,7 +399,8 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) except: # pragma: no cover self.gz_index += 1 self.gz = self.gz_methods[self.gz_index] - raise GalSimError("None of the options for gunzipping were successful.") + else: # pragma: no cover + raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': while self.bz2_index < len(self.bz2_methods): try: @@ -405,9 +410,10 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) except: # pragma: no cover self.bz2_index += 1 self.bz2 = self.bz2_methods[self.bz2_index] - raise GalSimError("None of the options for bunzipping were successful.") + else: # pragma: no cover + raise GalSimError("None of the options for bunzipping were successful.") else: - raise ValueError("Unknown file_compression") + raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) # There is a bug in pyfits where they don't add the size of the variable length array # to the TFORMx header keywords. They should have size at the end of them. @@ -573,8 +579,8 @@ def write(image, file_name=None, dir=None, hdu_list=None, clobber=True, compress from ._pyfits import pyfits if image.iscomplex: - raise ValueError("Cannot write complex Images to a fits file. " - "Write image.real and image.imag separately.") + raise GalSimValueError("Cannot write complex Images to a fits file. " + "Write image.real and image.imag separately.", image) file_compress, pyfits_compress = _parse_compression(compression,file_name) @@ -622,8 +628,8 @@ def writeMulti(image_list, file_name=None, dir=None, hdu_list=None, clobber=True from ._pyfits import pyfits if any(image.iscomplex for image in image_list if isinstance(image, Image)): - raise ValueError("Cannot write complex Images to a fits file. " - "Write image.real and image.imag separately.") + raise GalSimValueError("Cannot write complex Images to a fits file. " + "Write image.real and image.imag separately.", image_list) file_compress, pyfits_compress = _parse_compression(compression,file_name) @@ -689,18 +695,18 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, if isinstance(image_list, np.ndarray): is_all_numpy = True if image_list.dtype.kind == 'c': - raise ValueError("Cannot write complex numpy arrays to a fits file. " - "Write array.real and array.imag separately.") + raise GalSimValueError("Cannot write complex numpy arrays to a fits file. " + "Write array.real and array.imag separately.", image_list) elif all(isinstance(item, np.ndarray) for item in image_list): is_all_numpy = True if any(a.dtype.kind == 'c' for a in image_list): - raise ValueError("Cannot write complex numpy arrays to a fits file. " - "Write array.real and array.imag separately.") + raise GalSimValueError("Cannot write complex numpy arrays to a fits file. " + "Write array.real and array.imag separately.", image_list) else: is_all_numpy = False if any(im.iscomplex for im in image_list): - raise ValueError("Cannot write complex images to a fits file. " - "Write image.real and image.imag separately.") + raise GalSimValueError("Cannot write complex images to a fits file. " + "Write image.real and image.imag separately.", image_list) file_compress, pyfits_compress = _parse_compression(compression,file_name) @@ -785,7 +791,7 @@ def writeFile(file_name, hdu_list, dir=None, clobber=True, compression='auto'): if pyfits_compress and compression != 'auto': # If compression is auto and it determined that it should use rice, then we # should presume that the hdus were already rice compressed, so we can ignore it here. - raise ValueError("Compression %s is invalid for writeFile"%compression) + raise GalSimValueError("Compression %s is invalid for writeFile",compression) _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress) @@ -1293,10 +1299,10 @@ def __setitem__(self, key, value): if isinstance(value, tuple): # header[key] = (value, comment) syntax if not (0 < len(value) <= 2): - raise ValueError( + raise GalSimValueError( 'A Header item may be set with either a scalar value, ' 'a 1-tuple containing a scalar value, or a 2-tuple ' - 'containing a scalar value and comment string.') + 'containing a scalar value and comment string.', value) elif len(value) == 1: self.header.update(key, value[0]) else: diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 195e63decd3..e1b63b58fff 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -58,7 +58,7 @@ from . import _galsim from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args -from .errors import GalSimError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimWarning class GSObject(object): @@ -603,8 +603,8 @@ def calculateMomentRadius(self, size=None, scale=None, centroid=None, rtype='det @returns an estimate of the radius in physical units (or both estimates if rtype == 'both') """ - if rtype not in ['trace', 'det', 'both']: - raise ValueError("rtype must be one of 'trace', 'det', or 'both'") + if rtype not in ('trace', 'det', 'both'): + raise GalSimValueError("Invalid rtype.", rtype, ('trace', 'det', 'both')) if hasattr(self, 'sigma'): if rtype == 'both': @@ -1486,7 +1486,7 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N # Check that image is sane if image is not None and not isinstance(image, Image): - raise ValueError("image is not an Image instance") + raise GalSimValueError("image is not an Image instance", image) # Make sure (gain, area, exptime) have valid values: if gain <= 0.: @@ -1496,8 +1496,9 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N if exptime <= 0.: raise GalSimRangeError("Invalid exptime <= 0.", exptime, 0., None) - if method not in ['auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb']: - raise ValueError("Invalid method name = %s"%method) + if method not in ('auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb'): + raise GalSimValueError("Invalid method name", method, + ('auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb')) # Check that the user isn't convolving by a Pixel already. This is almost always an error. if method == 'auto' and isinstance(self, Convolution): @@ -1678,7 +1679,7 @@ def drawReal(self, image, add_to_image=False): """ from .image import ImageD, ImageF if image.wcs is None or not image.wcs.isPixelScale(): - raise ValueError("drawReal requires an image with a PixelScale wcs") + raise GalSimValueError("drawReal requires an image with a PixelScale wcs", image) if image.dtype in [ np.float64, np.float32 ] and not add_to_image and image.iscontiguous: self._drawReal(image) @@ -1841,7 +1842,7 @@ def drawFFT(self, image, add_to_image=False): @returns The total flux drawn inside the image bounds. """ if image.wcs is None or not image.wcs.isPixelScale(): - raise ValueError("drawPhot requires an image with a PixelScale wcs") + raise GalSimValueError("drawPhot requires an image with a PixelScale wcs", image) kimage, wrap_size = self.drawFFT_makeKImage(image) self._drawKImage(kimage) @@ -2060,7 +2061,7 @@ def drawPhot(self, image, gain=1., add_to_image=False, # Make sure the image is set up to have unit pixel scale and centered at 0,0. if image.wcs is None or not image.wcs.isPixelScale(): - raise ValueError("drawPhot requires an image with a PixelScale wcs") + raise GalSimValueError("drawPhot requires an image with a PixelScale wcs", image) if sensor is None: sensor = Sensor() @@ -2199,7 +2200,7 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, from .image import Image # Make sure provided image is complex if image is not None and not image.iscomplex: - raise ValueError("Provided image must be complex") + raise GalSimValueError("Provided image must be complex", image) # Possibly get the scale from image. if image is not None and scale is None: diff --git a/galsim/hsm.py b/galsim/hsm.py index 34d27cd73da..519c6d4337d 100644 --- a/galsim/hsm.py +++ b/galsim/hsm.py @@ -63,12 +63,9 @@ from .bounds import BoundsI from .shear import Shear from .image import Image, ImageI, ImageF, ImageD -from .errors import GalSimError +from .errors import GalSimError, GalSimValueError, GalSimHSMError -class GalSimHSMError(GalSimError): - pass - class ShapeData(object): """A class to contain the outputs of using the HSM shape and moments measurement routines. @@ -465,7 +462,7 @@ def _convertMask(image, weight=None, badpix=None): # also make sure there are no negative values if np.any(weight.array < 0) == True: - raise ValueError("Weight image cannot contain negative values!") + raise GalSimValueError("Weight image cannot contain negative values.", weight) # if weight is an ImageI, then we can use it as the mask image: if weight.dtype == np.int32: diff --git a/galsim/image.py b/galsim/image.py index 70d28519435..3e271b34dfe 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -27,7 +27,7 @@ from .bounds import BoundsI, BoundsD from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities -from .errors import GalSimError, GalSimBoundsError +from .errors import GalSimError, GalSimBoundsError, GalSimValueError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -291,8 +291,7 @@ def __init__(self, *args, **kwargs): # Figure out what dtype we want: dtype = Image._alias_dtypes.get(dtype,dtype) if dtype is not None and dtype not in Image.valid_dtypes: - raise ValueError("dtype must be one of "+str(Image.valid_dtypes)+ - ". Instead got "+str(dtype)) + raise GalSimValueError("Invlid dtype.", dtype, Image.valid_dtypes) if array is not None: if not isinstance(array, np.ndarray): raise TypeError("array must be a numpy.ndarray instance") @@ -303,10 +302,8 @@ def __init__(self, *args, **kwargs): dtype = Image._alias_dtypes[dtype] array = array.astype(dtype, copy=copy) elif dtype not in Image._cpp_valid_dtypes: - raise ValueError( - "array's dtype.type must be one of "+str(Image._cpp_valid_dtypes)+ - ". Instead got "+str(array.dtype.type)+". Or can set "+ - "dtype explicitly.") + raise GalSimValueError("Invalid dtype of provided array.", array.dtype, + Image._cpp_valid_dtypes) elif copy: array = np.array(array) else: @@ -726,7 +723,7 @@ def wrap(self, bounds, hermitian=False): raise ValueError("hermitian == 'y' requires bounds.ymin == 0") _galsim.wrapImage(self._image, bounds._b, False, True) else: - raise ValueError("Invalid value for hermitian: %s"%hermitian) + raise GalSimValueError("Invalid value for hermitian", hermitian, (False, 'x', 'y')) return ret; def _wrap(self, bounds, hermx, hermy): @@ -864,9 +861,10 @@ def calculate_fft(self): @returns an Image instance with the k-space image. """ if self.wcs is None: - raise ValueError("calculate_fft requires that the scale be set.") + raise GalSimError("calculate_fft requires that the scale be set.") if not self.wcs.isPixelScale(): - raise ValueError("calculate_fft requires that the image has a PixelScale wcs.") + raise GalSimValueError("calculate_fft requires that the image has a PixelScale wcs.", + self.wcs) if not self.bounds.isDefined(): raise ValueError("calculate_fft requires that the image have defined bounds.") @@ -911,13 +909,15 @@ def calculate_inverse_fft(self): @returns an ImageD instance with the real-space image. """ if self.wcs is None: - raise ValueError("calculate_inverse_fft requires that the scale be set.") + raise GalSimError("calculate_inverse_fft requires that the scale be set.") if not self.wcs.isPixelScale(): - raise ValueError("calculate_inverse_fft requires that the image has a PixelScale wcs.") + raise GalSimValueError("calculate_inverse_fft requires that the image has a " + "PixelScale wcs.", self.wcs) if not self.bounds.isDefined(): raise ValueError("calculate_inverse_fft requires that the image have defined bounds.") if not self.bounds.includes(0,0): - raise ValueError("calculate_inverse_fft requires that the image includes point (0,0)") + raise GalSimBoundsError("calculate_inverse_fft requires that the image includes (0,0)", + PositionI(0,0), self.bounds) No2 = max(self.bounds.xmax, -self.bounds.ymin, self.bounds.ymax) @@ -1252,7 +1252,7 @@ def addValue(self, *args, **kwargs): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise GalSimError("Attempt to set value of an undefined image") + raise ValueError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): @@ -1272,7 +1272,7 @@ def fill(self, value): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise GalSimError("Attempt to set values of an undefined image") + raise ValueError("Attempt to set values of an undefined image") self._fill(value) def _fill(self, value): @@ -1297,7 +1297,7 @@ def invertSelf(self): if self.isconst: raise ValueError("Cannot modify the values of an immutable Image") if not self.bounds.isDefined(): - raise GalSimError("Attempt to set values of an undefined image") + raise ValueError("Attempt to set values of an undefined image") self._invertSelf() def _invertSelf(self): @@ -1394,7 +1394,7 @@ def calculateMomentRadius(self, center=None, flux=None, rtype='det'): (or both estimates if rtype == 'both'). """ if rtype not in ['trace', 'det', 'both']: - raise ValueError("rtype must be one of 'trace', 'det', or 'both'") + raise GalSimValueError("Invalid rtype.", rtype, ('trace', 'det', 'both')) if center is None: center = self.true_center @@ -1578,12 +1578,12 @@ def ImageCD(*args, **kwargs): # Define a utility function to be used by the arithmetic functions below def check_image_consistency(im1, im2, integer=False): if integer and not im1.isinteger: - raise ValueError("Image must have integer values, not %s"%im1.dtype) + raise GalSimValueError("Image must have integer values.",im1) if isinstance(im2, Image): if im1.array.shape != im2.array.shape: raise ValueError("Image shapes are inconsistent") if integer and not im2.isinteger: - raise ValueError("Image must have integer values, not %s"%im2.dtype) + raise GalSimValueError("Image must have integer values.",im2) def Image_add(self, other): check_image_consistency(self, other) diff --git a/galsim/integ.py b/galsim/integ.py index 1a79823783f..66f357dffc8 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -24,7 +24,7 @@ from functools import reduce from . import _galsim -from .errors import GalSimError, GalSimRangeError +from .errors import GalSimError, GalSimRangeError, GalSimValueError def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): """Integrate a 1-dimensional function from min to max. @@ -113,7 +113,7 @@ def midptRule(f, xs): @returns Midpoint rule approximation to the integral. """ if len(xs) < 2: - raise ValueError("Not enough points for midptRule integration") + raise GalSimValueError("Not enough points for midptRule integration", xs) x, xp = xs[:2] result = f(x)*(xp-x) for x, xp, xpp in zip(xs[0:-2], xs[1:-1], xs[2:]): @@ -131,7 +131,7 @@ def trapzRule(f, xs): @returns Trapezoidal rule approximation to the integral. """ if len(xs) < 2: - raise ValueError("Not enough points for trapzRule integration") + raise GalSimValueError("Not enough points for trapzRule integration", xs) x, xp = xs[:2] result = 0.5*f(x)*(xp-x) for x, xp, xpp in zip(xs[0:-2], xs[1:-1], xs[2:]): @@ -193,8 +193,7 @@ def __init__(self, rule): def calculateWaves(self, bandpass): if len(bandpass.wave_list) < 0: - raise AttributeError("Bandpass does not have attribute `wave_list` needed by " + - "SampleIntegrator.") + raise GalSimValueError("Provided bandpass must have defined wave_list", bandpass) return bandpass.wave_list diff --git a/galsim/interpolant.py b/galsim/interpolant.py index bf89d6ed399..9f696474843 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -26,6 +26,7 @@ from . import _galsim from .gsparams import GSParams from .utilities import lazy_property +from .errors import GalSimValueError class Interpolant(object): """A base class that defines how interpolation should be done. @@ -77,8 +78,8 @@ def from_name(name, tol=1.e-4, gsparams=None): try: n = int(name[7:]) except: - raise ValueError("Invalid Lanczos specification %s. "%name + - "Should look like lanczosN, where N is an integer") + raise GalSimValueError("Invalid Lanczos specification. Should look like " + "lanczosN, where N is an integer", name) return Lanczos(n, conserve_dc, tol, gsparams) elif name.lower() == 'linear': return Linear(tol, gsparams) @@ -91,7 +92,9 @@ def from_name(name, tol=1.e-4, gsparams=None): elif name.lower() == 'sinc': return SincInterpolant(tol, gsparams) else: - raise ValueError("Invalid Interpolant name %s."%name) + raise GalSimValueError("Invalid Interpolant name %s.",name, + ('linear', 'cubic', 'quintic', 'lanczosN', 'nearest', 'delta', + 'sinc')) def __getstate__(self): d = self.__dict__.copy() diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 11ef2380c88..a59631baa66 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -34,7 +34,7 @@ from .random import BaseDeviate from . import _galsim from . import fits -from .errors import GalSimError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimWarning class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -283,7 +283,8 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # make sure image is really an image and has a float type if image.dtype != np.float32 and image.dtype != np.float64: - raise ValueError("Supplied image does not have dtype of float32 or float64!") + raise GalSimValueError("Supplied image must have dtype = float32 or float64.", + image.dtype) # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.bounds.isDefined(): @@ -292,8 +293,8 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # check what normalization was specified for the image: is it an image of surface # brightness, or flux? if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): - raise ValueError(("Invalid normalization requested: '%s'. Expecting one of 'flux', "+ - "'f', 'surface brightness', or 'sb'.") % normalization) + raise GalSimValueError("Invalid normalization requested.", normalization, + ('flux', 'f', 'surface brightness', 'sb')) # set up the interpolants if none was provided by user, or check that the user-provided ones # are of a valid type @@ -395,9 +396,10 @@ def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, else: pad_image = pad_image._view() if not isinstance(pad_image, Image): - raise ValueError("Supplied pad_image is not an Image!") + raise GalSimValueError("Supplied pad_image must be an Image.", pad_image) if pad_image.dtype != np.float32 and pad_image.dtype != np.float64: - raise ValueError("Supplied pad_image is not one of the allowed types!") + raise GalSimValueError("Invalid dtype for Supplied pad_image.", pad_image.dtype, + (np.float32, np.float64)) if pad_factor <= 0.: raise GalSimRangeError("Invalid pad_factor <= 0 in InterpolatedImage", pad_factor, 0.) @@ -482,9 +484,9 @@ def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): if use_cache: InterpolatedImage._cache_noise_pad[noise_pad] = noise else: - raise ValueError( + raise GalSimValueError( "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename "+ - "containing an image to use to make a CorrelatedNoise!") + "containing an image to use to make a CorrelatedNoise.", noise_pad) else: if noise_pad < 0.: @@ -841,9 +843,9 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, # make sure real_kimage, imag_kimage are really `Image`s, are floats, and are # congruent. if not isinstance(real_kimage, Image): - raise ValueError("Supplied real_kimage is not an Image instance") + raise GalSimValueError("Supplied real_kimage is not an Image instance", real_kimage) if not isinstance(imag_kimage, Image): - raise ValueError("Supplied imag_kimage is not an Image instance") + raise GalSimValueError("Supplied imag_kimage is not an Image instance", imag_kimage) if real_kimage.bounds != imag_kimage.bounds: raise ValueError("Real and Imag kimages must have same bounds.") if real_kimage.wcs != imag_kimage.wcs: @@ -854,11 +856,11 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, if real_kimage is not None or imag_kimage is not None: raise ValueError("Cannot provide both kimage and real_kimage/imag_kimage") if not kimage.iscomplex: - raise ValueError("Supplied kimage is not complex") + raise GalSimValueError("Supplied kimage is not complex", kimage) # Make sure wcs is a PixelScale. if kimage.wcs is not None and not kimage.wcs.isPixelScale(): - raise ValueError("kimage wcs must be PixelScale or None.") + raise GalSimValueError("kimage wcs must be PixelScale or None.", kimage.wcs) self._kimage = kimage.copy() self._gsparams = GSParams.check(gsparams) diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index 7ff8629eebb..10e9dbf8489 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -32,7 +32,7 @@ from . import utilities from . import integ from . import _galsim -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -170,7 +170,7 @@ class PowerSpectrum(object): def __init__(self, e_power_function=None, b_power_function=None, delta2=False, units=arcsec): # Check that at least one power function is not None if e_power_function is None and b_power_function is None: - raise AttributeError( + raise ValueError( "At least one of e_power_function or b_power_function must be provided.") self.e_power_function = e_power_function @@ -191,7 +191,8 @@ def __init__(self, e_power_function=None, b_power_function=None, delta2=False, u # if the string is invalid, this raises a reasonable error message. units = AngleUnit.from_name(units) if not isinstance(units, AngleUnit): - raise ValueError("units must be either an AngleUnit or a string") + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) if units == arcsec: self.scale = 1 @@ -231,7 +232,8 @@ def _get_scale_fac(self, units): # if the string is invalid, this raises a reasonable error message. units = AngleUnit.from_name(units) if not isinstance(units, AngleUnit): - raise ValueError("units must be either an AngleUnit or a string") + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) return units / arcsec def _get_bandlimit_func(self, bandlimit): @@ -242,7 +244,8 @@ def _get_bandlimit_func(self, bandlimit): elif bandlimit is None: return lambda k, kmax: 1.0 else: - raise ValueError("Unrecognized option for band limit!") + raise GalSimValueError("Unrecognized option for band limit!", bandlimit, + (None, 'soft', 'hard')) def _get_pk(self, power_function, k_max, bandlimit_func): if power_function is None: @@ -265,7 +268,7 @@ def _get_pk(self, power_function, k_max, bandlimit_func): else: return lambda k : power_function(k) * bandlimit_func(k, k_max) - def buildGrid(self, grid_spacing=None, ngrid=None, rng=None, interpolant=None, + def buildGrid(self, grid_spacing, ngrid, rng=None, interpolant=None, center=PositionD(0,0), units=arcsec, get_convergence=False, kmax_factor=1, kmin_factor=1, bandlimit="hard", variance=None): """Generate a realization of the current power spectrum on the specified grid. @@ -461,26 +464,24 @@ def buildGrid(self, grid_spacing=None, ngrid=None, rng=None, interpolant=None, @returns the tuple (g1,g2[,kappa]), where each is a 2-d NumPy array and kappa is included iff `get_convergence` is set to True. """ - # Check problem cases for regular grid of points - if grid_spacing is None or ngrid is None: - raise ValueError("Both a spacing and a size are required for buildGrid.") # Check for validity of integer values if not isinstance(ngrid, int): if ngrid != int(ngrid): - raise ValueError("ngrid must be an integer") + raise GalSimValueError("ngrid must be an integer", ngrid) ngrid = int(ngrid) if not isinstance(kmin_factor, int): if kmin_factor != int(kmin_factor): - raise ValueError("kmin_factor must be an integer") + raise GalSimValueError("kmin_factor must be an integer", kmin_factor) kmin_factor = int(kmin_factor) if not isinstance(kmax_factor, int): if kmax_factor != int(kmax_factor): - raise ValueError("kmax_factor must be an integer") + raise GalSimValueError("kmax_factor must be an integer", kmax_factor) kmax_factor = int(kmax_factor) # Check if center is a PositionD if not isinstance(center, PositionD): - raise ValueError("center argument for buildGrid must be a PositionD instance") + raise GalSimValueError("center argument for buildGrid must be a PositionD instance", + center) # Automatically convert units to arcsec at the outset, then forget about it. This is # because PowerSpectrum by default wants to work in arsec, and all power functions are @@ -588,11 +589,10 @@ def _convert_power_function(self, pf, pf_str): pf = utilities.math_eval('lambda k : ' + pf) pf(1.0) except Exception as e: - raise ValueError( + raise GalSimValueError( "String %s must either be a valid filename or something that "%pf_str+ "can eval to a function of k.\n"+ - "Input provided: {0}\n".format(origpf)+ - "Caught error: {0}".format(e)) + "Caught error: {0}".format(e), origpf) # Check that the function is sane. # Note: Only try tests below if it's not a LookupTable. @@ -1482,15 +1482,15 @@ def _generate_power_array(self, power_function): mink = np.min(k) maxk = np.max(k) if mink < power_function.x_min or maxk > power_function.x_max: - raise ValueError( - "LookupTable P(k) is not defined for full k range on grid, %f= 1"%args.job) + raise GalSimRangeError("Invalid job number. Must be >= 1", args.job, 1, args.njobs) if args.job > args.njobs: - raise ValueError("Invalid job number %d. Must be <= njobs (%d)"%(args.job,args.njobs)) + raise GalSimRangeError("Invalid job number. Must be <= njobs",args.job, 1, args.njobs) # Parse the integer verbosity level from the command line args into a logging_level string logging_levels = { 0: logging.CRITICAL, diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 0a94782ea7e..0d1473f6a04 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,7 +80,7 @@ from .wcs import PixelScale from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -387,9 +387,11 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good # Sanity checks if pupil_plane_im.array.shape[0] != pupil_plane_im.array.shape[1]: - raise ValueError("We require square input pupil plane arrays!") + raise GalSimValueError("Input pupil_plane_im must be square.", + pupil_plane_im.array.shape) if pupil_plane_im.array.shape[0] % 2 == 1: - raise ValueError("Even-sized input arrays are required for the pupil plane!") + raise GalSimValueError("Input pupil_plane_im must have even sizes.", + pupil_plane_im.array.shape) # Set the scale, priority is: # 1. pupil_plane_scale kwarg @@ -1146,7 +1148,7 @@ def __init__(self, screen_list, lam, t0=0.0, exptime=0.0, time_step=0.025, flux= self.img = np.zeros(self.aper.illuminated.shape, dtype=np.float64) if self.exptime < 0: - raise ValueError("Cannot integrate PSF for negative time.") + raise GalSimRangeError("Cannot integrate PSF for negative time.", self.exptime, 0.) self._ii_pad_factor = ii_pad_factor diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 7153f491846..d19e70aef32 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -26,7 +26,7 @@ from . import utilities from . import fft from . import zernike -from .errors import GalSimRangeError, GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimWarning class AtmosphericScreen(object): @@ -652,7 +652,7 @@ def __init__(self, diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, raise TypeError("Cannot pass in individual aberrations and array!") # Aberrations were passed in, so check for right number of entries. if len(aberrations) <= 2: - raise ValueError("Aberrations keyword must have length > 2") + raise GalSimValueError("Aberrations keyword must have length > 2", aberrations) # Check for non-zero value in first two places. Probably a mistake. if aberrations[0] != 0.0: import warnings diff --git a/galsim/position.py b/galsim/position.py index 65599251158..5bed903f3a0 100644 --- a/galsim/position.py +++ b/galsim/position.py @@ -127,7 +127,7 @@ def __add__(self, other): if isinstance(other,Bounds): return other + self elif not isinstance(other,Position): - raise ValueError("Can only add a Position to a %s"%self.__class__.__name__) + raise TypeError("Can only add a Position to a %s"%self.__class__.__name__) elif isinstance(other, self.__class__): return self.__class__(self.x + other.x, self.y + other.y) else: @@ -135,7 +135,7 @@ def __add__(self, other): def __sub__(self, other): if not isinstance(other,Position): - raise ValueError("Can only subtract a Position from a %s"%self.__class__.__name__) + raise TypeError("Can only subtract a Position from a %s"%self.__class__.__name__) elif isinstance(other, self.__class__): return self.__class__(self.x - other.x, self.y - other.y) else: @@ -164,7 +164,7 @@ class PositionD(Position): def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) if self.x != float(self.x) or self.y != float(self.y): - raise ValueError("PositionD must be initialized with float values") + raise TypeError("PositionD must be initialized with float values") self.x = float(self.x) self.y = float(self.y) @@ -177,7 +177,7 @@ def _check_scalar(self, other, op): if other == float(other): return except (TypeError, ValueError): pass - raise ValueError("Can only %s a PositionD by float values"%op) + raise TypeError("Can only %s a PositionD by float values"%op) class PositionI(Position): @@ -190,7 +190,7 @@ class PositionI(Position): def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) if self.x != int(self.x) or self.y != int(self.y): - raise ValueError("PositionI must be initialized with integer values") + raise TypeError("PositionI must be initialized with integer values") self.x = int(self.x) self.y = int(self.y) @@ -205,4 +205,4 @@ def _check_scalar(self, other, op): if other == int(other): return except (TypeError, ValueError): pass - raise ValueError("Can only %s a PositionI by integer values"%op) + raise TypeError("Can only %s a PositionI by integer values"%op) diff --git a/galsim/pse.py b/galsim/pse.py index 507dedb7c9c..1d71bcbed08 100644 --- a/galsim/pse.py +++ b/galsim/pse.py @@ -27,7 +27,7 @@ import os import sys -from .errors import GalSimError +from .errors import GalSimError, GalSimValueError class PowerSpectrumEstimator(object): """ @@ -210,12 +210,12 @@ def estimate(self, g1, g2, weight_EE=False, weight_BB=False, theory_func=None): if g1.shape != g2.shape: raise ValueError("g1 and g2 grids do not have the same shape!") if g1.shape[0] != g1.shape[1]: - raise ValueError("Input shear arrays are not square!") + raise GalSimValueError("Input shear arrays must be square.", g1.shape) if g1.shape[0] != self.N: - raise ValueError("Input shear array size is not correct!") + raise GalSimValueError("Input shear array size is not correct!", g1.shape) if not isinstance(weight_EE, bool) or not isinstance(weight_BB, bool): - raise ValueError("Input weight flags must be bools!") + raise TypeError("Input weight flags must be bools!") # Transform g1+j*g2 into Fourier space and rotate into E-B, then separate into E and B. EB = np.fft.ifft2(self.eb_rot * np.fft.fft2(g1 + 1j*g2)) diff --git a/galsim/random.py b/galsim/random.py index 9b66314557c..995700eb21b 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -24,7 +24,7 @@ import weakref from . import _galsim -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimValueError class BaseDeviate(object): """Base class for all the various random deviates. @@ -700,11 +700,10 @@ def __init__(self, seed=None, function=None, x_min=None, # but we'd like to throw reasonable errors in that case anyway function(0.6) # A value unlikely to be a singular point of a function except Exception as e: - raise ValueError( + raise GalSimValueError( "String function must either be a valid filename or something that "+ "can eval to a function of x.\n"+ - "Input provided: {0}\n".format(self.__function)+ - "Caught error: {0}".format(e)) + "Caught error: {0}".format(e), self.__function) else: self.__function = weakref.ref(function) # Save the inputs to be used in repr # Check that the function is actually a function @@ -745,7 +744,7 @@ def __init__(self, seed=None, function=None, x_min=None, # Check that the probability is nonnegative if not np.all(pdf >= 0.): - raise ValueError('Negative probability passed to DistDeviate: %s'%function) + raise GalSimValueError('Negative probability found in DistDeviate.',function) # Compute the cumulative distribution function = int(pdf(x),x) cdf = np.cumsum(pdf) @@ -753,7 +752,7 @@ def __init__(self, seed=None, function=None, x_min=None, # Quietly renormalize the probability if it wasn't already normalized totalprobability = cdf[-1] if totalprobability < 0.: - raise ValueError('Negative probability passed to DistDeviate: %s'%function) + raise GalSimValueError('Negative probability found in DistDeviate.',function) cdf /= totalprobability self._inverse_cdf = LookupTable(cdf, xarray, interpolant='linear') diff --git a/galsim/real.py b/galsim/real.py index cba58fdd7ec..569bdbf4353 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -39,7 +39,7 @@ from .chromatic import ChromaticSum from .position import PositionD from .utilities import doc_inherit -from .errors import GalSimError +from .errors import GalSimError, GalSimValueError HST_area = 45238.93416 # Area of HST primary mirror in cm^2 from Synphot User's Guide. @@ -220,11 +220,11 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, # Get the index to use in the catalog if index is not None: if id is not None or random: - raise AttributeError('Too many methods for selecting a galaxy!') + raise ValueError('Too many methods for selecting a galaxy!') use_index = index elif id is not None: if random: - raise AttributeError('Too many methods for selecting a galaxy!') + raise ValueError('Too many methods for selecting a galaxy!') use_index = real_galaxy_catalog.getIndexForID(id) elif random: ud = UniformDeviate(self.rng) @@ -237,7 +237,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, # Pick another one to try. use_index = int(real_galaxy_catalog.nobjects * ud()) else: - raise AttributeError('No method specified for selecting a galaxy!') + raise ValueError('No method specified for selecting a galaxy!') if logger: logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) @@ -629,7 +629,7 @@ def getIndexForID(self, id): if id in self.ident: return self.ident.index(id) else: - raise ValueError('ID %s not found in list of IDs'%id) + raise GalSimValueError('ID not found in list of IDs',id, self.ident) def preload(self): """Preload the files into memory. @@ -687,9 +687,9 @@ def getBandpass(self): try: bp = real_galaxy_bandpasses[self.band[0].upper()] except KeyError: - raise ValueError("Bandpass not found. To use bandpass '{0}', please add an entry to " - "the galsim.real.real_galaxy_bandpasses " - "dictionary.".format(self.band[0])) + raise GalSimValueError("Bandpass not found. To use this bandpass, please add an entry " + "to the galsim.real.real_galaxy_bandpasses dictionary.", + self.band[0], real_galaxy_bandpasses.keys()) return Bandpass(bp[0], wave_type='nm', zeropoint=bp[1]) def getGalImage(self, i): @@ -836,8 +836,8 @@ def _parse_files_dirs(file_name, image_dir, sample): use_sample = None else: use_sample = sample - if use_sample != '25.2' and use_sample != '23.5': - raise ValueError("Sample name not recognized: %s"%use_sample) + if use_sample not in ('23.5', '25.2'): + raise GalSimValueError("Sample name not recognized.",use_sample, ('23.5', '25.2')) # after that piece of code, use_sample is either "23.5", "25.2" (if using one of the default # catalogs) or it is still None, if a file_name was given. @@ -1000,7 +1000,7 @@ class ChromaticRealGalaxy(ChromaticSum): There are no additional methods for ChromaticRealGalaxy beyond the usual ChromaticObject methods. """ - def __init__(self, real_galaxy_catalogs=None, index=None, id=None, random=False, rng=None, + def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng=None, gsparams=None, logger=None, **kwargs): from .random import BaseDeviate, UniformDeviate from .bounds import BoundsI @@ -1013,23 +1013,20 @@ def __init__(self, real_galaxy_catalogs=None, index=None, id=None, random=False, "is not a BaseDeviate") self.rng = rng - if real_galaxy_catalogs is None: - raise ValueError("No RealGalaxyCatalog(s) specified!") - # Get the index to use in the catalog if index is not None: if id is not None or random: - raise AttributeError('Too many methods for selecting a galaxy!') + raise ValueError('Too many methods for selecting a galaxy!') use_index = index elif id is not None: if random: - raise AttributeError('Too many methods for selecting a galaxy!') + raise ValueError('Too many methods for selecting a galaxy!') use_index = real_galaxy_catalogs[0].getIndexForID(id) elif random: uniform_deviate = UniformDeviate(self.rng) use_index = int(real_galaxy_catalogs[0].nobjects * uniform_deviate()) else: - raise AttributeError('No method specified for selecting a galaxy!') + raise ValueError('No method specified for selecting a galaxy!') if logger: logger.debug('ChromaticRealGalaxy %d: Start ChromaticRealGalaxy constructor.', use_index) diff --git a/galsim/scene.py b/galsim/scene.py index cbc043af625..838a66bb5bf 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -26,7 +26,7 @@ import os from .real import RealGalaxy, RealGalaxyCatalog -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -172,8 +172,9 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, from .real import _parse_files_dirs self.use_real = use_real - if exclusion_level not in ['none', 'bad_stamp', 'bad_fits', 'marginal']: - raise ValueError("Invalid value of exclusion_level: %s"%exclusion_level) + if exclusion_level not in ('none', 'bad_stamp', 'bad_fits', 'marginal'): + raise GalSimValueError("Invalid value of exclusion_level.", exclusion_level, + ('none', 'bad_stamp', 'bad_fits', 'marginal')) # Start by parsing the file name full_file_name, _, self.use_sample = _parse_files_dirs(file_name, dir, sample) @@ -483,8 +484,8 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= if gal_type is None: gal_type = 'real' - if gal_type not in ['real', 'parametric']: - raise ValueError("Invalid galaxy type %r"%gal_type) + if gal_type not in ('real', 'parametric'): + raise GalSimValueError("Invalid galaxy type %r", gal_type, ('real', 'parametric')) # We'll set these up if and when we need them. self._bandpass = None @@ -873,8 +874,8 @@ def _makeSingleGalaxy(cosmos_catalog, index, gal_type, noise_pad_size=5, deep=Fa elif gal_type != 'parametric': raise ValueError("Only 'parametric' galaxy type is allowed when use_real == False") - if gal_type not in ['real', 'parametric']: - raise ValueError("Invalid galaxy type %r"%gal_type) + if gal_type not in ('real', 'parametric'): + raise GalSimValueError("Invalid galaxy type", gal_type, ('real', 'parametric')) if gal_type == 'real' and rng is None: rng = BaseDeviate() diff --git a/galsim/sed.py b/galsim/sed.py index 6c3524dce9c..00b8da81578 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -31,7 +31,7 @@ from . import integ from . import dcr from .utilities import WeakMethod, lazy_property, combine_wave_list -from .errors import GalSimError, GalSimRangeError +from .errors import GalSimError, GalSimValueError, GalSimRangeError class SED(object): """Object to represent the spectral energy distributions of stars and galaxies. @@ -128,14 +128,14 @@ def __init__(self, spec, wave_type, flux_type, redshift=0., fast=True, self._flux_type = flux_type # Need to save the original for repr # Parse the various options for wave_type if isinstance(wave_type, str): - if wave_type.lower() in ['nm', 'nanometer', 'nanometers']: + if wave_type.lower() in ('nm', 'nanometer', 'nanometers'): self.wave_type = 'nm' self.wave_factor = 1. - elif wave_type.lower() in ['a', 'ang', 'angstrom', 'angstroms']: + elif wave_type.lower() in ('a', 'ang', 'angstrom', 'angstroms'): self.wave_type = 'Angstrom' self.wave_factor = 10. else: - raise ValueError("Unknown wave_type '{0}'".format(wave_type)) + raise GalSimValueError("Unknown wave_type", wave_type, ('nm', 'Angstrom')) else: self.wave_type = wave_type try: @@ -173,7 +173,8 @@ def __init__(self, spec, wave_type, flux_type, redshift=0., fast=True, self.flux_type = '1' self.spectral = False else: - raise ValueError("Unknown flux_type '{0}'".format(flux_type)) + raise GalSimValueError("Unknown flux_type", flux_type, + ('flambda', 'fnu', 'fphotons', '1')) else: self.flux_type = flux_type self.spectral = self.check_spectral() @@ -320,16 +321,15 @@ def _initialize_spec(self): except ArithmeticError: test_value = 0 except Exception as e: - raise ValueError( + raise GalSimValueError( "String spec must either be a valid filename or something that "+ "can eval to a function of wave.\n" + - "Input provided: {0!r}\n".format(self._orig_spec) + - "Caught error: {0}".format(e)) + "Caught error: {0}".format(e), self._orig_spec) from numbers import Real if not isinstance(test_value, Real): - raise ValueError("The given SED function, %r, did not return a valid" - " number at test wavelength %s: got %s"%( - self._spec, 700.0, test_value)) + raise GalSimValueError("The given SED function did not return a valid number " + "at test wavelength %s: got %s"%(700.0, test_value), + self._orig_spec) else: self._spec = self._orig_spec @@ -741,7 +741,10 @@ def calculateFlux(self, bandpass): slop = 1e-6 # nm if (self.blue_limit > bandpass.blue_limit + slop or self.red_limit < bandpass.red_limit - slop): - raise ValueError("SED undefined within Bandpass") + raise GalSimRangeError("Bandpass is not completely within defined wavelength " + "range for this SED.", + (bandpass.blue_limit, bandpass.red_limit), + self.blue_limit, self.red_limit) x, _, _ = combine_wave_list(self, bandpass) return np.trapz(bandpass(x) * self(x), x) else: diff --git a/galsim/sensor.py b/galsim/sensor.py index f260f4b27a1..fa54ec9d2e9 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -182,9 +182,9 @@ def __init__(self, name='lsst_itl_8', strength=1.0, rng=None, diffusion_factor=1 # A bit kludgy, but it works self.treering_func = LookupTable(x=[0.0,1.0], f=[0.0,0.0], interpolant='linear') elif not isinstance(treering_func, LookupTable): - raise ValueError("treering_func must be a galsim.LookupTable") + raise TypeError("treering_func must be a galsim.LookupTable") if not isinstance(treering_center, PositionD): - raise ValueError("treering_center must be a galsim.PositionD") + raise TypeError("treering_center must be a galsim.PositionD") # Now we read in the absorption length table: abs_file = os.path.join(meta_data.share_dir, 'sensors', 'abs_length.dat') diff --git a/galsim/shapelet.py b/galsim/shapelet.py index 88f4f55d2b9..1a2689a353a 100644 --- a/galsim/shapelet.py +++ b/galsim/shapelet.py @@ -28,6 +28,8 @@ from .image import Image from .utilities import doc_inherit from . import _galsim +from .errors import GalSimValueError + class Shapelet(GSObject): """A class describing polar shapelet surface brightness profiles. @@ -272,8 +274,8 @@ def fit(cls, sigma, order, image, center=None, normalization='flux', gsparams=No center = PositionD(center.x,center.y) if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): - raise ValueError(("Invalid normalization requested: '%s'. Expecting one of 'flux', "+ - "'f', 'surface brightness' or 'sb'.") % normalization) + raise GalSimValueError("Invalid normalization requested.", normalization, + ('flux', 'f', 'surface brightneess', 'sb')) ret = Shapelet(sigma, order, bvec=None, gsparams=gsparams) diff --git a/galsim/table.py b/galsim/table.py index 977af4d9de9..35e50acddbd 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -28,7 +28,7 @@ from .utilities import lazy_property from .position import PositionD from .bounds import BoundsD -from .errors import GalSimRangeError, GalSimBoundsError +from .errors import GalSimRangeError, GalSimBoundsError, GalSimValueError class LookupTable(object): """ @@ -98,17 +98,18 @@ def __init__(self, x=None, f=None, interpolant=None, x_log=False, f_log=False): if interpolant is None: interpolant = 'spline' else: - if interpolant not in ['spline', 'linear', 'ceil', 'floor', 'nearest']: - raise ValueError("Unknown interpolant: %s" % interpolant) + if interpolant not in ('spline', 'linear', 'ceil', 'floor', 'nearest'): + raise GalSimValueError("Unknown interpolant", interpolant, + ('spline', 'linear', 'ceil', 'floor', 'nearest')) self.interpolant = interpolant # Sanity checks if len(x) != len(f): raise ValueError("Input array lengths don't match") if interpolant == 'spline' and len(x) < 3: - raise ValueError("Input arrays too small to spline interpolate") + raise GalSimValueError("Input arrays too small to spline interpolate", x) if interpolant in ['linear', 'ceil', 'floor', 'nearest'] and len(x) < 2: - raise ValueError("Input arrays too small to interpolate") + raise GalSimValueError("Input arrays too small to interpolate", x) # turn x and f into numpy arrays so that all subsequent math is possible (unlike for # lists, tuples). Also make sure the dtype is float @@ -121,11 +122,11 @@ def __init__(self, x=None, f=None, interpolant=None, x_log=False, f_log=False): self._x_min = self.x[0] self._x_max = self.x[-1] if self._x_min == self._x_max: - raise ValueError("All x values are equal") + raise GalSimValueError("All x values are equal", x) if self.x_log and self.x[0] <= 0.: - raise ValueError("Cannot interpolate in log(x) when table contains x<=0!") + raise GalSimValueError("Cannot interpolate in log(x) when table contains x<=0.", x) if self.f_log and np.any(self.f <= 0.): - raise ValueError("Cannot interpolate in log(f) when table contains f<=0!") + raise GalSimValueError("Cannot interpolate in log(f) when table contains f<=0.", f) @lazy_property def _tab(self): @@ -168,7 +169,8 @@ def __call__(self, x): # Handle the log(x) if necessary if self.x_log: if np.any(np.asarray(x) <= 0.): - raise ValueError("Cannot interpolate x<=0 when using log(x) interpolation.") + raise GalSimValueError("Cannot interpolate x<=0 when using log(x) interpolation.", + x) x = np.log(x) x = np.asarray(x) @@ -177,7 +179,7 @@ def __call__(self, x): else: dimen = len(x.shape) if dimen > 2: - raise ValueError("Arrays with dimension larger than 2 not allowed!") + raise GalSimValueError("Arrays with dimension larger than 2 not allowed.", x) elif dimen == 2: f = np.empty_like(x.ravel(), dtype=float) xx = x.astype(float,copy=False).ravel() @@ -282,7 +284,8 @@ def from_file(cls, file_name, interpolant='spline', x_log=False, f_log=False, am except (ImportError, AttributeError, CParserError): # pragma: no cover data = np.loadtxt(file_name).transpose() if data.shape[0] != 2: - raise ValueError("File %s provided for LookupTable does not have 2 columns"%file_name) + raise GalSimValueError("File provided for LookupTable does not have 2 columns", + file_name) x=data[0] f=data[1] if amplitude != 1.0: @@ -418,8 +421,8 @@ class LookupTable2D(object): `edge_mode='constant'`. [default: 0] """ def __init__(self, x, y, f, interpolant='linear', edge_mode='raise', constant=0): - if edge_mode not in ['raise', 'wrap', 'constant']: - raise ValueError("Unknown edge_mode: {:0}".format(edge_mode)) + if edge_mode not in ('raise', 'wrap', 'constant'): + raise GalSimValueError("Unknown edge_mode.", edge_mode, ('raise', 'wrap', 'constant')) self.x = np.ascontiguousarray(x, dtype=float) self.y = np.ascontiguousarray(y, dtype=float) @@ -428,15 +431,18 @@ def __init__(self, x, y, f, interpolant='linear', edge_mode='raise', constant=0) dx = np.diff(self.x) dy = np.diff(self.y) - if not (all(dx > 0) and all(dy > 0)): - raise ValueError("x and y input grids are not strictly increasing.") + if not all(dx > 0): + raise GalSimValueError("x input grids is not strictly increasing.", x) + if not all(dy > 0): + raise GalSimValueError("y input grids is not strictly increasing.", y) fshape = self.f.shape if fshape != (len(x), len(y)): raise ValueError("Shape of `f` must be (len(`x`), len(`y`)).") - if interpolant not in ['linear', 'ceil', 'floor', 'nearest']: - raise ValueError("Unknown interpolant: %s" % interpolant) + if interpolant not in ('linear', 'ceil', 'floor', 'nearest'): + raise GalSimValueError("Unknown interpolant.", interpolant, + ('linear', 'ceil', 'floor', 'nearest')) self.interpolant = interpolant diff --git a/galsim/utilities.py b/galsim/utilities.py index 57af8304b2b..b3ea4374240 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -25,7 +25,7 @@ import weakref import numpy as np -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning def roll2d(image, shape): @@ -293,7 +293,8 @@ def _convertPositions(pos, units, func): # if the string is invalid, this raises a reasonable error message. units = AngleUnit.from_name(units) if not isinstance(units, AngleUnit): - raise ValueError("units must be either an AngleUnit or a string") + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) # Convert pos to arcsec if units != arcsec: @@ -395,7 +396,7 @@ def thin_tabulated_values(x, f, rel_err=1.e-4, trim_zeros=True, preserve_range=T if rel_err <= 0 or rel_err >= 1: raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): - raise ValueError("input x is not sorted.") + raise GalSimValueError("input x is not sorted.", x) # Check for trivial noop. if len(x) <= 2: @@ -493,7 +494,7 @@ def old_thin_tabulated_values(x, f, rel_err=1.e-4, preserve_range=False): # prag if rel_err <= 0 or rel_err >= 1: raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): - raise ValueError("input x is not sorted.") + raise GalSimValueError("input x is not sorted.", x) # Check for trivial noop. if len(x) <= 2: @@ -1033,7 +1034,7 @@ def resize(self, maxsize): root[1][0] = link root[1] = link else: - raise ValueError("Invalid maxsize: {0:}".format(maxsize)) + raise GalSimValueError("Invalid maxsize.", maxsize) # http://stackoverflow.com/questions/2891790/pretty-printing-of-numpy-array @@ -1332,9 +1333,9 @@ def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): # Note: we do not require that the type be an int, as long as the value is consistent with # an integer value (i.e., it could be a float 1.0 or 1). if not n-int(n) == 0 or n < 1: - raise ValueError("n must be an integer >= 1.") + raise GalSimValueError("n must be an integer >= 1.", n) if not n_choices-int(n_choices) == 0 or n_choices < 1: - raise ValueError("n_choices must be an integer >= 1.") + raise GalSimValueError("n_choices must be an integer >= 1.", n_choices) # Sanity check the input weight. if weight is not None: @@ -1344,7 +1345,8 @@ def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): (len(weight), n_choices)) if np.min(weight)<0 or np.max(weight)>1 or np.any(np.isnan(weight)) or \ np.any(np.isinf(weight)): - raise ValueError("Supplied weights include values outside [0,1] or inf/NaN values!") + raise GalSimValueError("Supplied weights include values outside [0,1] or inf/NaN.", + weight) # We first make a random list of integer indices. index = np.zeros(n) diff --git a/galsim/zernike.py b/galsim/zernike.py index c24bec1dfad..1bc34487203 100644 --- a/galsim/zernike.py +++ b/galsim/zernike.py @@ -22,6 +22,7 @@ import numpy as np from .utilities import LRU_Cache, binomial, horner2d, nCr, lazy_property +from .errors import GalSimValueError # Some utilities for working with Zernike polynomials @@ -101,7 +102,7 @@ def _zern_coef_array(n, m, obscuration, shape): elif obscuration == 0: coefs = np.array(_zern_rho_coefs(n, m), dtype=np.complex128) else: - raise ValueError("Illegal obscuration: {}".format(obscuration)) + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) coefs /= _zern_norm(n, m) if m < 0: coefs *= -1j @@ -576,7 +577,7 @@ def zernikeRotMatrix(jmax, theta): if m_jmax != 0: n_jmaxp1, m_jmaxp1 = noll_to_zern(jmax+1) if n_jmax == n_jmaxp1 and abs(m_jmaxp1) == abs(m_jmax): - raise ValueError("Cannot construct Zernike rotation matrix for jmax={}".format(jmax)) + raise GalSimValueError("Cannot construct Zernike rotation matrix for this jmax.", jmax) R = np.zeros((jmax+1, jmax+1), dtype=np.float64) R[0, 0] = 1.0 diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index cf688bae611..611a80c540b 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -1177,7 +1177,7 @@ def test_analytic_integrator(): "Analytic integrator doesn't match sample integrator") # Test that attempting to use SampleIntegrator with analytic sed, bandpass raises an Error: - with assert_raises(AttributeError): + with assert_raises(ValueError): final1.drawImage(band1, integrator=galsim.integ.SampleIntegrator(rule=galsim.integ.trapzRule)) diff --git a/tests/test_detectors.py b/tests/test_detectors.py index 25f27377652..1d49fc5409c 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -494,7 +494,7 @@ def test_Persistence_basic(): # Test for different lengths of imgs and coeffs im_new = im.copy() - with assert_raises(TypeError): + with assert_raises(ValueError): im_new.applyPersistence(im_prev, [0.2, 0.3]) # Test for a single image and coeffs as a float diff --git a/tests/test_image.py b/tests/test_image.py index 4ebb4b26480..ca402aa1292 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1969,8 +1969,8 @@ def test_BoundsI_init_with_non_pure_ints(): "Cannot initialize a BoundI with float array elements" # Using non-integers should raise a ValueError - assert_raises(ValueError, galsim.BoundsI, *bound_arr_flt_nonint) - assert_raises(ValueError, galsim.BoundsI, + assert_raises(TypeError, galsim.BoundsI, *bound_arr_flt_nonint) + assert_raises(TypeError, galsim.BoundsI, xmin=bound_arr_flt_nonint[0], xmax=bound_arr_flt_nonint[1], ymin=bound_arr_flt_nonint[2], ymax=bound_arr_flt_nonint[3]) diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index 9fe1cf460af..49b351f076b 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -231,7 +231,7 @@ def test_exceptions(): """ # What if it receives as input something that is not an Image? Give it a GSObject to check. g = galsim.Gaussian(sigma=1.) - with assert_raises((ValueError, AttributeError)): + with assert_raises((TypeError, AttributeError)): galsim.InterpolatedImage(g) # What if Image does not have a scale set, but scale keyword is not specified? im = galsim.ImageF(5, 5) diff --git a/tests/test_lensing.py b/tests/test_lensing.py index 801a5c7517a..acde177d197 100644 --- a/tests/test_lensing.py +++ b/tests/test_lensing.py @@ -843,16 +843,16 @@ def test_tabulated(): assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.), x_log=True, f_log=True) # Check some invalid PowerSpectrum parameters - assert_raises(AttributeError, galsim.PowerSpectrum) - assert_raises(AttributeError, galsim.PowerSpectrum, delta2=True) - assert_raises(AttributeError, galsim.PowerSpectrum, delta2=True, units='radians') + assert_raises(ValueError, galsim.PowerSpectrum) + assert_raises(ValueError, galsim.PowerSpectrum, delta2=True) + assert_raises(ValueError, galsim.PowerSpectrum, delta2=True, units='radians') assert_raises(ValueError, galsim.PowerSpectrum, e_power_function=tab, units='inches') assert_raises(ValueError, galsim.PowerSpectrum, e_power_function=tab, units=True) assert_raises(ValueError, galsim.PowerSpectrum, e_power_function='not_a_file') assert_raises(ValueError, galsim.PowerSpectrum, b_power_function='not_a_file') - assert_raises(ValueError, ps_tab.buildGrid) - assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7) - assert_raises(ValueError, ps_tab.buildGrid, ngrid=10) + assert_raises(TypeError, ps_tab.buildGrid) + assert_raises(TypeError, ps_tab.buildGrid, grid_spacing=1.7) + assert_raises(TypeError, ps_tab.buildGrid, ngrid=10) assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10.5) assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10, kmin_factor=2.5) assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10, kmax_factor=1.5) diff --git a/tests/test_pse.py b/tests/test_pse.py index a532df8e3b6..b7e698bb23e 100644 --- a/tests/test_pse.py +++ b/tests/test_pse.py @@ -172,8 +172,8 @@ def test_PSE_weight(): np.testing.assert_allclose(P_eb3[1:]/P_theory[1:], 0., atol=zero_tolerance, err_msg='Weighted PSE found EB cross-power') - assert_raises(ValueError, pse.estimate, g1, g2, weight_EE=8) - assert_raises(ValueError, pse.estimate, g1, g2, weight_BB='yes') + assert_raises(TypeError, pse.estimate, g1, g2, weight_EE=8) + assert_raises(TypeError, pse.estimate, g1, g2, weight_BB='yes') if __name__ == "__main__": diff --git a/tests/test_real.py b/tests/test_real.py index 852ba1342de..a2b76b17b73 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -73,10 +73,10 @@ def test_real_galaxy_ideal(): rg_1 = galsim.RealGalaxy(rgc, index = ind_fake, rng = galsim.BaseDeviate(1234)) rg_2 = galsim.RealGalaxy(rgc, random=True) assert_raises(TypeError, galsim.RealGalaxy, rgc, index=ind_fake, rng='foo') - assert_raises(AttributeError, galsim.RealGalaxy, rgc, index=ind_fake, id=0) - assert_raises(AttributeError, galsim.RealGalaxy, rgc, index=ind_fake, random=True) - assert_raises(AttributeError, galsim.RealGalaxy, rgc, id=0, random=True) - assert_raises(AttributeError, galsim.RealGalaxy, rgc) + assert_raises(ValueError, galsim.RealGalaxy, rgc, index=ind_fake, id=0) + assert_raises(ValueError, galsim.RealGalaxy, rgc, index=ind_fake, random=True) + assert_raises(ValueError, galsim.RealGalaxy, rgc, id=0, random=True) + assert_raises(ValueError, galsim.RealGalaxy, rgc) # Different RNGs give different random galaxies. rg_3 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(12345)) rg_4 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(67890)) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 99701d28f6f..26a2d404cb7 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -75,7 +75,7 @@ def test_pos(): assert_raises(TypeError, galsim.PositionI, x=11, z=23) assert_raises(TypeError, galsim.PositionI, x=11) assert_raises(TypeError, galsim.PositionI, 11) - assert_raises(ValueError, galsim.PositionI, 11, 23.5) + assert_raises(TypeError, galsim.PositionI, 11, 23.5) assert_raises(TypeError, galsim.PositionD, 11, 23, 9) assert_raises(TypeError, galsim.PositionD, x=11, z=23) @@ -125,12 +125,12 @@ def test_pos(): assert pd9 == 0*pd1 assert isinstance(pd9, galsim.PositionD) - assert_raises(ValueError, pd1.__mul__, "11") - assert_raises(ValueError, pd1.__mul__, None) - assert_raises(ValueError, pd1.__div__, "11e") - assert_raises(ValueError, pi1.__mul__, "11e") - assert_raises(ValueError, pi1.__mul__, None) - assert_raises(ValueError, pi1.__div__, 11.5) + assert_raises(TypeError, pd1.__mul__, "11") + assert_raises(TypeError, pd1.__mul__, None) + assert_raises(TypeError, pd1.__div__, "11e") + assert_raises(TypeError, pi1.__mul__, "11e") + assert_raises(TypeError, pi1.__mul__, None) + assert_raises(TypeError, pi1.__div__, 11.5) do_pickle(pi1) do_pickle(pd1) @@ -210,7 +210,7 @@ def test_bounds(): assert_raises(TypeError, galsim.BoundsI, xmin=11, xmax=23, ymin=17, ymax=50, z=23) assert_raises(TypeError, galsim.BoundsI, xmin=11, xmax=50) assert_raises(TypeError, galsim.BoundsI, 11) - assert_raises(ValueError, galsim.BoundsI, 11, 23.5, 17, 50.9) + assert_raises(TypeError, galsim.BoundsI, 11, 23.5, 17, 50.9) assert_raises(TypeError, galsim.BoundsD, 11, 23, 9) assert_raises(TypeError, galsim.BoundsD, 11, 23, 9, 12, 59) @@ -237,13 +237,13 @@ def test_bounds(): assert bd1.withBorder(4.1) == galsim.BoundsD(6.9,27.1,12.9,54.1) assert bd1.withBorder(0) == galsim.BoundsD(11,23,17,50) assert bd1.withBorder(-1) == galsim.BoundsD(12,22,18,49) - assert_raises(ValueError, bi1.withBorder, 'blue') - assert_raises(ValueError, bi1.withBorder, 4.1) - assert_raises(ValueError, bi1.withBorder, '4') - assert_raises(ValueError, bi1.withBorder, None) - assert_raises(ValueError, bd1.withBorder, 'blue') - assert_raises(ValueError, bd1.withBorder, '4.1') - assert_raises(ValueError, bd1.withBorder, None) + assert_raises(TypeError, bi1.withBorder, 'blue') + assert_raises(TypeError, bi1.withBorder, 4.1) + assert_raises(TypeError, bi1.withBorder, '4') + assert_raises(TypeError, bi1.withBorder, None) + assert_raises(TypeError, bd1.withBorder, 'blue') + assert_raises(TypeError, bd1.withBorder, '4.1') + assert_raises(TypeError, bd1.withBorder, None) # Check expand assert bi1.expand(2) == galsim.BoundsI(5,29,0,67) From 6fce80d751b2cca3fc833f6d00169dc152303196 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 19:04:44 -0400 Subject: [PATCH 08/96] Use GalSimSEDError for cases of dimensional/spectral SED errors (#755) --- galsim/__init__.py | 3 ++- galsim/chromatic.py | 28 +++++++++++++++------------- galsim/errors.py | 16 ++++++++++++++++ galsim/sed.py | 4 ++-- tests/test_sed.py | 2 +- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index f8b0159ac92..71dd74cee96 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -102,7 +102,8 @@ from .table import LookupTable, LookupTable2D # Exception and Warning classes -from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimHSMError +from .errors import GalSimError, GalSimRangeError, GalSimValueError +from .errors import GalSimSEDError, GalSimHSMError from .errors import GalSimWarning # Image diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 2b4f3926ea1..13f8b26f8db 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,7 +35,7 @@ from .utilities import lazy_property from . import utilities from . import integ -from .errors import GalSimError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -395,7 +395,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # Store the last bandpass used and any extra kwargs. self._last_bp = bandpass if self.SED.dimensionless: - raise ValueError("Can only draw ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.SED) # setup output image using fiducial profile wave0, prof0 = self._fiducial_profile(bandpass) @@ -470,7 +470,7 @@ def drawKImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): from .table import LookupTable if self.SED.dimensionless: - raise ValueError("Can only drawK ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only drawK ChromaticObjects with spectral SEDs.", self.SED) # setup output image (semi-arbitrarily using the bandpass effective wavelength) prof0 = self.evaluateAtWavelength(bandpass.effective_wavelength) @@ -671,7 +671,8 @@ def calculateFlux(self, bandpass): @returns the flux through the bandpass. """ if self.SED.dimensionless: - raise ValueError("Cannot calculate flux of dimensionless ChromaticObject.") + raise GalSimSEDError("Cannot calculate flux of dimensionless ChromaticObject.", + self.SED) return self.SED.calculateFlux(bandpass) def calculateMagnitude(self, bandpass): @@ -687,7 +688,8 @@ def calculateMagnitude(self, bandpass): @returns the bandpass magnitude. """ if self.SED.dimensionless: - raise ValueError("Cannot calculate magnitude of dimensionless ChromaticObject.") + raise GalSimSEDError("Cannot calculate magnitude of dimensionless ChromaticObject.", + self.SED) return self.SED.calculateMagnitude(bandpass) # Add together `ChromaticObject`s and/or `GSObject`s @@ -1247,7 +1249,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # Store the last bandpass used. self._last_bp = bandpass if self.SED.dimensionless: - raise ValueError("Can only draw ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.SED) int_im = self._get_interp_image(bandpass, image=image, integrator=integrator, **kwargs) image = int_im.drawImage(image=image, **kwargs) @@ -1648,7 +1650,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # Store the last bandpass used. self._last_bp = bandpass if self.SED.dimensionless: - raise ValueError("Can only draw ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.SED) if isinstance(self.original, InterpolatedChromaticObject): # Pass self._flux_ratio, which *could* depend on wavelength, to _get_interp_image, # where it will be used to reweight the stored images. @@ -1845,7 +1847,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # Store the last bandpass used. self._last_bp = bandpass if self.SED.dimensionless: - raise ValueError("Can only draw ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.SED) add_to_image = kwargs.pop('add_to_image', False) # Use given add_to_image for the first one, then add_to_image=False for the rest. image = self.obj_list[0].drawImage( @@ -2064,7 +2066,7 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', iimult=None, # Store the last bandpass used. self._last_bp = bandpass if self.SED.dimensionless: - raise ValueError("Can only draw ChromaticObjects with spectral SEDs.") + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.SED) # `ChromaticObject.drawImage()` can just as efficiently handle separable cases. if self.separable: image = ChromaticObject.drawImage(self, bandpass, image=image, **kwargs) @@ -2223,7 +2225,7 @@ class ChromaticDeconvolution(ChromaticObject): """ def __init__(self, obj, **kwargs): if not obj.SED.dimensionless: - raise ValueError("Cannot deconvolve by spectral ChromaticObject.") + raise GalSimSEDError("Cannot deconvolve by spectral ChromaticObject.", obj.SED) self._obj = obj self.kwargs = kwargs self.separable = obj.separable @@ -2279,7 +2281,7 @@ class ChromaticAutoConvolution(ChromaticObject): """ def __init__(self, obj, **kwargs): if not obj.SED.dimensionless: - raise ValueError("Cannot autoconvolve spectral ChromaticObject.") + raise GalSimSEDError("Cannot autoconvolve spectral ChromaticObject.", obj.SED) self._obj = obj self.kwargs = kwargs self.separable = obj.separable @@ -2336,7 +2338,7 @@ class ChromaticAutoCorrelation(ChromaticObject): """ def __init__(self, obj, **kwargs): if not obj.SED.dimensionless: - raise ValueError("Cannot autocorrelate spectral ChromaticObject.") + raise GalSimSEDError("Cannot autocorrelate spectral ChromaticObject.", obj.SED) self._obj = obj self.kwargs = kwargs self.separable = obj.separable @@ -2402,7 +2404,7 @@ class ChromaticFourierSqrtProfile(ChromaticObject): def __init__(self, obj, **kwargs): import math if not obj.SED.dimensionless: - raise ValueError("Cannot take Fourier sqrt of spectral ChromaticObject.") + raise GalSimSEDError("Cannot take Fourier sqrt of spectral ChromaticObject.", obj.SED) self._obj = obj self.kwargs = kwargs self.separable = obj.separable diff --git a/galsim/errors.py b/galsim/errors.py index 0d84e59826e..3c8457a3233 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -62,6 +62,7 @@ def __init__(self, message, value, min, max=None): self.min = min self.max = max + class GalSimBoundsError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input position is outside of the allowed bounds. @@ -78,6 +79,21 @@ def __init__(self, message, pos, bounds): self.bounds = bounds +class GalSimSEDError(GalSimError): + """A GalSim-specific exception class indicating an attempt to do something invalid for the + kind of SED that is present. Typically involving a dimensionless SED where a spectral SED + is required (or vice versa). + + Attrubutes: + + sed = the invalid SED + """ + def __init__(self, message, sed): + message += " SED: {0!s}.".format(sed) + super().__init__(message) + self.sed = sed + + class GalSimHSMError(GalSimError): """A GalSim-specific exception class indicating some kind of failure of the HSM algorithms """ diff --git a/galsim/sed.py b/galsim/sed.py index 00b8da81578..2a67de14c12 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -31,7 +31,7 @@ from . import integ from . import dcr from .utilities import WeakMethod, lazy_property, combine_wave_list -from .errors import GalSimError, GalSimValueError, GalSimRangeError +from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimSEDError class SED(object): """Object to represent the spectral energy distributions of stars and galaxies. @@ -301,7 +301,7 @@ def _initialize_spec(self): self._const = False if isinstance(self._orig_spec, (int, float)): if not self.dimensionless: - raise ValueError("Attempt to set spectral SED using float or integer.") + raise GalSimSEDError("Attempt to set spectral SED using float or integer.", self) self._const = True self._spec = lambda w: float(self._orig_spec) elif isinstance(self._orig_spec, basestring): diff --git a/tests/test_sed.py b/tests/test_sed.py index d9932d547d4..3b23d038da7 100644 --- a/tests/test_sed.py +++ b/tests/test_sed.py @@ -414,7 +414,7 @@ def test_SED_init(): assert_raises(TypeError, galsim.SED, spec=lambda w:1.0, flux_type='bar') assert_raises(TypeError, galsim.SED, spec=lambda w:1.0) assert_raises(ValueError, galsim.SED, spec='wave', wave_type=units.Hz, flux_type='2') - assert_raises(ValueError, galsim.SED, 1.0, 'nm', 'fphotons') + assert_raises(galsim.GalSimSEDError, galsim.SED, 1.0, 'nm', 'fphotons') # These should succeed. galsim.SED(spec='wave', wave_type='nm', flux_type='flambda') galsim.SED(spec='wave/wave', wave_type='nm', flux_type='flambda') From bd1e6307a806630d42c6d2af833a1163482a8a7b Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 19:06:36 -0400 Subject: [PATCH 09/96] Use GalSimImmutableError for cases of trying to modify an immutable Image (#755) --- galsim/__init__.py | 1 + galsim/errors.py | 13 ++++++++++ galsim/image.py | 18 +++++++------- tests/test_image.py | 60 +++++++++++++++------------------------------ 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index 71dd74cee96..c1e72b3ef14 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -103,6 +103,7 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError +from .errors import GalSimImmutableError from .errors import GalSimSEDError, GalSimHSMError from .errors import GalSimWarning diff --git a/galsim/errors.py b/galsim/errors.py index 3c8457a3233..d219b417331 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -100,6 +100,19 @@ class GalSimHSMError(GalSimError): pass +class GalSimImmutableError(GalSimError): + """A GalSim-specific exception class indicating an attempt to modify an immutable image. + + Attrubutes: + + image = the image that the user attempted to modify + """ + def __init__(self, message, image): + message += " Image: {0!s}".format(image) + super().__init__(message) + self.image = image + + class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ diff --git a/galsim/image.py b/galsim/image.py index 3e271b34dfe..72510bcbcc8 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -27,7 +27,7 @@ from .bounds import BoundsI, BoundsD from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities -from .errors import GalSimError, GalSimBoundsError, GalSimValueError +from .errors import GalSimError, GalSimBoundsError, GalSimValueError, GalSimImmutableError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -582,7 +582,7 @@ def resize(self, bounds, wcs=None): which means keep the existing wcs] """ if self.isconst: - raise ValueError("Cannot modify an immutable Image") + raise GalSimImmutableError("Cannot modify an immutable Image", self) if not isinstance(bounds, BoundsI): raise TypeError("bounds must be a galsim.BoundsI instance") self._array = self._make_empty(shape=bounds.numpyShape(), dtype=self.dtype) @@ -613,7 +613,7 @@ def setSubImage(self, bounds, rhs): This is equivalent to self[bounds] = rhs """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) self.subImage(bounds).copyFrom(rhs) def __getitem__(self, *args): @@ -960,7 +960,7 @@ def copyFrom(self, rhs): """Copy the contents of another image """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if self.bounds.numpyShape() != rhs.bounds.numpyShape(): raise ValueError("Trying to copy images that are not the same shape (%s -> %s)"%( rhs.bounds, self.bounds)) @@ -1225,7 +1225,7 @@ def setValue(self, *args, **kwargs): This is equivalent to self[x,y] = rhs """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): raise GalSimError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, @@ -1250,7 +1250,7 @@ def addValue(self, *args, **kwargs): This is equivalent to self[x,y] += rhs """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): raise ValueError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, @@ -1270,7 +1270,7 @@ def fill(self, value): """Set all pixel values to the given `value` """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): raise ValueError("Attempt to set values of an undefined image") self._fill(value) @@ -1285,7 +1285,7 @@ def setZero(self): """Set all pixel values to zero. """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) self._fill(0) # This might be made faster with a C++ call to use memset def invertSelf(self): @@ -1295,7 +1295,7 @@ def invertSelf(self): on the output, rather than turning into inf. """ if self.isconst: - raise ValueError("Cannot modify the values of an immutable Image") + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): raise ValueError("Attempt to set values of an undefined image") self._invertSelf() diff --git a/tests/test_image.py b/tests/test_image.py index ca402aa1292..73675fca810 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -271,11 +271,11 @@ def test_Image_basic(): assert_raises(galsim.GalSimError,im1.view().__call__,ncol+1,nrow+1) # Also, setting values in something that should be const - assert_raises(ValueError,im1.view(make_const=True).setValue,1,1,1) - assert_raises(ValueError,im1.view(make_const=True).real.setValue,1,1,1) - assert_raises(ValueError,im1.view(make_const=True).imag.setValue,1,1,1) + assert_raises(galsim.GalSimImmutableError,im1.view(make_const=True).setValue,1,1,1) + assert_raises(galsim.GalSimImmutableError,im1.view(make_const=True).real.setValue,1,1,1) + assert_raises(galsim.GalSimImmutableError,im1.view(make_const=True).imag.setValue,1,1,1) if tchar[i][0] != 'C': - assert_raises(ValueError,im1.imag.setValue,1,1,1) + assert_raises(galsim.GalSimImmutableError,im1.imag.setValue,1,1,1) # Finally check for the wrong number of arguments in get/setitem assert_raises(TypeError,im1.__getitem__,1) @@ -1888,55 +1888,35 @@ def test_ConstImage_array_constness(): """ for i in range(ntypes): image = galsim.Image(ref_array.astype(types[i]), make_const=True) - try: - image.array[1, 2] = 666 - assert False, "Setting values in a const image.array should have raised an error." - # Apparently older numpy versions might raise a GalSimError, a ValueError, or a TypeError + # Apparently older numpy versions might raise a RuntimeError, a ValueError, or a TypeError # when trying to write to arrays that have writeable=False. # From the numpy 1.7.0 release notes: # Attempting to write to a read-only array (one with # ``arr.flags.writeable`` set to ``False``) used to raise either a - # GalSimError, ValueError, or TypeError inconsistently, depending on + # RuntimeError, ValueError, or TypeError inconsistently, depending on # which code path was taken. It now consistently raises a ValueError. - except (galsim.GalSimError, ValueError, TypeError): - pass - except: - assert False, "Unexpected error: "+str(sys.exc_info()[0]) + with assert_raises((RuntimeError, ValueError, TypeError)): + image.array[1, 2] = 666 - # Native image operations that are invalid just raise ValueError - try: + # Native image operations that are invalid just raise GalSimImmutableError + with assert_raises(galsim.GalSimImmutableError): image[1, 2] = 666 - assert False, "Setting values in a const image should have raised an error." - except ValueError: - pass - except: - assert False, "Unexpected error: "+str(sys.exc_info()[0]) - try: + with assert_raises(galsim.GalSimImmutableError): image.setValue(1,2,666) - assert False, "Calling setValue on a const image should have raised an error." - except ValueError: - pass - except: - assert False, "Unexpected error: "+str(sys.exc_info()[0]) - try: + with assert_raises(galsim.GalSimImmutableError): image[image.bounds] = image - assert False, "Setting subImage of a const image should have raised an error." - except ValueError: - pass - except: - assert False, "Unexpected error: "+str(sys.exc_info()[0]) # The rest are functions, so just use assert_raises. - assert_raises(ValueError, image.setValue, 1, 2, 666) - assert_raises(ValueError, image.setSubImage, image.bounds, image) - assert_raises(ValueError, image.addValue, 1, 2, 666) - assert_raises(ValueError, image.copyFrom, image) - assert_raises(ValueError, image.resize, image.bounds) - assert_raises(ValueError, image.fill, 666) - assert_raises(ValueError, image.setZero) - assert_raises(ValueError, image.invertSelf) + assert_raises(galsim.GalSimImmutableError, image.setValue, 1, 2, 666) + assert_raises(galsim.GalSimImmutableError, image.setSubImage, image.bounds, image) + assert_raises(galsim.GalSimImmutableError, image.addValue, 1, 2, 666) + assert_raises(galsim.GalSimImmutableError, image.copyFrom, image) + assert_raises(galsim.GalSimImmutableError, image.resize, image.bounds) + assert_raises(galsim.GalSimImmutableError, image.fill, 666) + assert_raises(galsim.GalSimImmutableError, image.setZero) + assert_raises(galsim.GalSimImmutableError, image.invertSelf) do_pickle(image) From da1b2c39d8a15721936ceca925ce0e646870e39f Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 19:08:24 -0400 Subject: [PATCH 10/96] Use GalSimUndefinedBoundsError for cases where a defined bounds is required, but the given bounds is undefined (#755) --- galsim/__init__.py | 2 +- galsim/bounds.py | 5 +++-- galsim/correlatednoise.py | 9 +++++---- galsim/errors.py | 7 +++++++ galsim/image.py | 13 ++++++++----- galsim/interpolatedimage.py | 5 +++-- tests/test_interpolatedimage.py | 2 +- tests/test_utilities.py | 8 ++++---- 8 files changed, 32 insertions(+), 19 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index c1e72b3ef14..500022891af 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -103,7 +103,7 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError -from .errors import GalSimImmutableError +from .errors import GalSimImmutableError, GalSimUndefinedBoundsError from .errors import GalSimSEDError, GalSimHSMError from .errors import GalSimWarning diff --git a/galsim/bounds.py b/galsim/bounds.py index bdcfedc337d..62974db2b71 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -23,6 +23,7 @@ from . import _galsim from .position import Position, PositionI, PositionD +from .errors import GalSimUndefinedBoundsError class Bounds(object): """A class for representing image bounds as 2D rectangles. @@ -189,7 +190,7 @@ def center(self): For a BoundsD, this is equivalent to true_center. """ if not self.isDefined(): - raise ValueError("center is invalid for an undefined Bounds") + raise GalSimUndefinedBoundsError("center is invalid for an undefined Bounds") return self._center @property @@ -200,7 +201,7 @@ def true_center(self): this may not necessarily be an integer PositionI. """ if not self.isDefined(): - raise ValueError("true_center is invalid for an undefined Bounds") + raise GalSimUndefinedBoundsError("true_center is invalid for an undefined Bounds") return PositionD((self.xmax + self.xmin)/2., (self.ymax + self.ymin)/2.) def includes(self, *args): diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 455c12a4596..aacb88eb224 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -26,7 +26,8 @@ from .random import BaseDeviate from .gsobject import GSObject from . import utilities -from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimUndefinedBoundsError +from .errors import GalSimWarning def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -215,7 +216,7 @@ def applyTo(self, image): if not isinstance(image, Image): raise TypeError("Input image argument must be a galsim.Image.") if not image.bounds.isDefined(): - raise ValueError("Input image argument must have defined bounds.") + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. @@ -312,7 +313,7 @@ def whitenImage(self, image): if not isinstance(image, Image): raise TypeError("Input image not a galsim.Image object") if not image.bounds.isDefined(): - raise ValueError("Input image argument must have defined bounds.") + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. @@ -396,7 +397,7 @@ def symmetrizeImage(self, image, order=4): if not isinstance(image, Image): raise TypeError("Input image not a galsim.Image object") if not image.bounds.isDefined(): - raise ValueError("Input image argument must have defined bounds.") + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") # Check that the input is square in shape. if image.array.shape[0] != image.array.shape[1]: diff --git a/galsim/errors.py b/galsim/errors.py index d219b417331..6d97ea573c9 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -79,6 +79,13 @@ def __init__(self, message, pos, bounds): self.bounds = bounds +class GalSimUndefinedBoundsError(GalSimError): + """A GalSim-specific exception class indicating an attempt to access the range of bounds + that have not yet been defined. + """ + pass + + class GalSimSEDError(GalSimError): """A GalSim-specific exception class indicating an attempt to do something invalid for the kind of SED that is present. Typically involving a dimensionless SED where a spectral SED diff --git a/galsim/image.py b/galsim/image.py index 72510bcbcc8..e3bb1ba7e24 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -28,6 +28,7 @@ from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities from .errors import GalSimError, GalSimBoundsError, GalSimValueError, GalSimImmutableError +from .errors import GalSimUndefinedBoundsError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -866,7 +867,8 @@ def calculate_fft(self): raise GalSimValueError("calculate_fft requires that the image has a PixelScale wcs.", self.wcs) if not self.bounds.isDefined(): - raise ValueError("calculate_fft requires that the image have defined bounds.") + raise GalSimUndefinedBoundsError("calculate_fft requires that the image have defined " + "bounds.") No2 = max(-self.bounds.xmin, self.bounds.xmax+1, -self.bounds.ymin, self.bounds.ymax+1) @@ -914,7 +916,8 @@ def calculate_inverse_fft(self): raise GalSimValueError("calculate_inverse_fft requires that the image has a " "PixelScale wcs.", self.wcs) if not self.bounds.isDefined(): - raise ValueError("calculate_inverse_fft requires that the image have defined bounds.") + raise GalSimUndefinedBoundsError("calculate_inverse_fft requires that the image have " + "defined bounds.") if not self.bounds.includes(0,0): raise GalSimBoundsError("calculate_inverse_fft requires that the image includes (0,0)", PositionI(0,0), self.bounds) @@ -1252,7 +1255,7 @@ def addValue(self, *args, **kwargs): if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): - raise ValueError("Attempt to set value of an undefined image") + raise GalSimUndefinedBoundsError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): @@ -1272,7 +1275,7 @@ def fill(self, value): if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): - raise ValueError("Attempt to set values of an undefined image") + raise GalSimUndefinedBoundsError("Attempt to set values of an undefined image") self._fill(value) def _fill(self, value): @@ -1297,7 +1300,7 @@ def invertSelf(self): if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): - raise ValueError("Attempt to set values of an undefined image") + raise GalSimUndefinedBoundsError("Attempt to set values of an undefined image") self._invertSelf() def _invertSelf(self): diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index a59631baa66..31573fdea27 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -34,7 +34,8 @@ from .random import BaseDeviate from . import _galsim from . import fits -from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError +from .errors import GalSimWarning class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -288,7 +289,7 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.bounds.isDefined(): - raise ValueError("Supplied image does not have bounds defined!") + raise GalSimUndefinedBoundsError("Supplied image does not have bounds defined.") # check what normalization was specified for the image: is it an image of surface # brightness, or flux? diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index 49b351f076b..c032289ba0f 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -240,7 +240,7 @@ def test_exceptions(): # Image must have bounds defined im = galsim.ImageF() im.scale = 1. - with assert_raises(ValueError): + with assert_raises(galsim.GalSimUndefinedBoundsError): galsim.InterpolatedImage(im) # Weird flux normalization im = galsim.ImageF(5, 5, scale=1.) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 26a2d404cb7..ef46b5c774b 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -306,10 +306,10 @@ def test_bounds(): assert galsim.BoundsD(23, 11, 17, 50) == galsim.BoundsD() assert galsim.BoundsD(11, 23, 50, 17) == galsim.BoundsD() - assert_raises(ValueError, getattr, galsim.BoundsI(), 'center') - assert_raises(ValueError, getattr, galsim.BoundsD(), 'center') - assert_raises(ValueError, getattr, galsim.BoundsI(), 'true_center') - assert_raises(ValueError, getattr, galsim.BoundsD(), 'true_center') + assert_raises(galsim.GalSimUndefinedBoundsError, getattr, galsim.BoundsI(), 'center') + assert_raises(galsim.GalSimUndefinedBoundsError, getattr, galsim.BoundsD(), 'center') + assert_raises(galsim.GalSimUndefinedBoundsError, getattr, galsim.BoundsI(), 'true_center') + assert_raises(galsim.GalSimUndefinedBoundsError, getattr, galsim.BoundsD(), 'true_center') do_pickle(bi1) do_pickle(bd1) From 2bfbe8f96bedd8f7a78d23571d00673a24251a22 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 22 Apr 2018 21:59:12 -0400 Subject: [PATCH 11/96] Use GalSimIncompatibleValuesError for combinations of values that are incompatible (#755) --- galsim/__init__.py | 2 +- galsim/bandpass.py | 11 ++++--- galsim/chromatic.py | 15 +++++---- galsim/correlatednoise.py | 8 +++-- galsim/detectors.py | 6 ++-- galsim/errors.py | 14 ++++++++ galsim/gsobject.py | 64 ++++++++++++++++++++++++++----------- galsim/hsm.py | 10 ++++-- galsim/image.py | 30 +++++++++++------ galsim/interpolatedimage.py | 25 +++++++++++---- galsim/lensing_ps.py | 9 +++--- galsim/noise.py | 9 ++++-- galsim/phase_psf.py | 16 +++++++--- galsim/phase_screens.py | 29 ++++++++++------- galsim/pse.py | 6 ++-- galsim/real.py | 27 +++++++++++----- galsim/scene.py | 14 +++++--- galsim/sed.py | 4 ++- galsim/shapelet.py | 5 +-- galsim/table.py | 12 ++++--- galsim/utilities.py | 52 +++++++++++++++++------------- 21 files changed, 248 insertions(+), 120 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index 500022891af..e12c83a77d5 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -104,7 +104,7 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError from .errors import GalSimImmutableError, GalSimUndefinedBoundsError -from .errors import GalSimSEDError, GalSimHSMError +from .errors import GalSimSEDError, GalSimHSMError, GalSimIncompatibleValuesError from .errors import GalSimWarning # Image diff --git a/galsim/bandpass.py b/galsim/bandpass.py index c122b5978a6..171409c1749 100644 --- a/galsim/bandpass.py +++ b/galsim/bandpass.py @@ -29,7 +29,7 @@ from . import integ from . import meta_data from .utilities import WeakMethod, combine_wave_list -from .errors import GalSimRangeError, GalSimValueError +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError class Bandpass(object): """Simple bandpass object, which models the transmission fraction of incident light as a @@ -465,7 +465,10 @@ def truncate(self, blue_limit=None, red_limit=None, relative_throughput=None, # Enforce the choice of a single mode of truncation. if relative_throughput is not None: if blue_limit is not None or red_limit is not None: - raise ValueError("Truncate using relative_throughput or red/blue_limit, not both!") + raise GalSimIncompatibleValuesError( + "Truncate using relative_throughput or red/blue_limit, not both!", + blue_limit=blue_limit, red_limit=red_limit, + relative_throughput=relative_throughput) if preserve_zp == 'auto': if relative_throughput is not None: preserve_zp = True @@ -498,9 +501,9 @@ def truncate(self, blue_limit=None, red_limit=None, relative_throughput=None, wave_list = wave_list[np.logical_and(wave_list >= blue_limit, wave_list <= red_limit) ] elif relative_throughput is not None: - raise ValueError( + raise GalSimIncompatibleValuesError( "Can only truncate with relative_throughput argument if throughput is " - + "a LookupTable") + "a LookupTable", relative_throughput=relative_throughput, throughput=self._orig_tp) if preserve_zp: return Bandpass(self._orig_tp, self.wave_type, blue_limit, red_limit, diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 13f8b26f8db..0c7d9a1c61c 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,7 +35,8 @@ from .utilities import lazy_property from . import utilities from . import integ -from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimIncompatibleValuesError +from .errors import GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -416,9 +417,9 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', **kwargs): # merge self.wave_list into bandpass.wave_list if using a sampling integrator if isinstance(integrator, integ.SampleIntegrator): if len(wave_list) < 2: - raise ValueError( - "Cannot use SampleIntegrator when Bandpass and SED are both " - "analytic.") + raise GalSimIncompatibleValuesError( + "Cannot use SampleIntegrator when Bandpass and SED are both analytic.", + integrator=integrator, bandpass=bandpass, sed=self.SED) bandpass = Bandpass(LookupTable(wave_list, bandpass(wave_list), interpolant='linear'), 'nm') @@ -1749,7 +1750,8 @@ def __init__(self, *args, **kwargs): dimensionless = all(a.dimensionless for a in args) spectral = all(a.spectral for a in args) if not (dimensionless or spectral): - raise ValueError("Cannot add dimensionless and spectral ChromaticObjects.") + raise GalSimIncompatibleValuesError( + "Cannot add dimensionless and spectral ChromaticObjects.", args=args) # Sort arguments into inseparable objects and groups of separable objects. Note that # separable groups are only identified if the constituent objects have the *same* SED even @@ -1931,7 +1933,8 @@ def __init__(self, *args, **kwargs): # Accumulate convolutant .SEDs. Check if more than one is spectral. nspectral = sum(arg.spectral for arg in args) if nspectral > 1: - raise ValueError("Cannot convolve more than one spectral ChromaticObject.") + raise GalSimIncompatibleValuesError( + "Cannot convolve more than one spectral ChromaticObject.", args=args) self.SED = args[0].SED for obj in args[1:]: self.SED *= obj.SED diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index aacb88eb224..e13be1772e7 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -27,7 +27,7 @@ from .gsobject import GSObject from . import utilities from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimUndefinedBoundsError -from .errors import GalSimWarning +from .errors import GalSimIncompatibleValuesError, GalSimWarning def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -1216,7 +1216,8 @@ def __init__(self, image, rng=None, scale=None, wcs=None, x_interpolant=None, # Set the wcs if necessary if wcs is not None: if scale is not None: - raise ValueError("Cannot provide both wcs and scale") + raise GalSimIncompatibleValuesError("Cannot provide both wcs and scale", + scale=scale, wcs=scale) if not wcs.isUniform(): raise GalSimValueError("Cannot provide non-uniform wcs", wcs) if not isinstance(wcs, BaseWCS): @@ -1480,7 +1481,8 @@ def __init__(self, variance, rng=None, scale=None, wcs=None, gsparams=None): if wcs is not None: if scale is not None: - raise ValueError("Cannot provide both wcs and scale") + raise GalSimIncompatibleValuesError("Cannot provide both wcs and scale", + scale=scale, wcs=wcs) if not isinstance(wcs, BaseWCS): raise TypeError("wcs must be a BaseWCS instance") if not wcs.isUniform(): diff --git a/galsim/detectors.py b/galsim/detectors.py index 2b2310d2315..6d7174e8d8b 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -24,7 +24,8 @@ import sys from .image import Image -from .errors import GalSimRangeError, GalSimValueError, GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimWarning def applyNonlinearity(self, NLfunc, *args): """ @@ -309,7 +310,8 @@ def applyPersistence(self,imgs,coeffs): @returns None """ if not len(imgs)==len(coeffs): - raise ValueError("The length of 'imgs' and 'coeffs' must be the same") + raise GalSimIncompatibleValuesError("The length of 'imgs' and 'coeffs' must be the same", + imgs=imgs, coeffs=coeffs) for img,coeff in zip(imgs,coeffs): self += coeff*img diff --git a/galsim/errors.py b/galsim/errors.py index 6d97ea573c9..1be975d2ac5 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -86,6 +86,20 @@ class GalSimUndefinedBoundsError(GalSimError): pass +class GalSimIncompatibleValuesError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that 2 or more user-input values are + incompatible as given. + + Attrubutes: + + values = a dict of {name : value} giving the values that in combination are invalid. + """ + def __init__(self, message, **kwargs): + self.values = kwargs + message += " Values {0!s}".format(self.values) + super().__init__(message) + + class GalSimSEDError(GalSimError): """A GalSim-specific exception class indicating an attempt to do something invalid for the kind of SED that is present. Typically involving a dimensionless SED where a spectral SED diff --git a/galsim/gsobject.py b/galsim/gsobject.py index e1b63b58fff..cad2a84b940 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -58,7 +58,8 @@ from . import _galsim from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args -from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimWarning class GSObject(object): @@ -1014,25 +1015,32 @@ def _setup_image(self, image, nx, ny, bounds, add_to_image, dtype, odd=False): # Check validity of nx,ny,bounds: if image is not None: if bounds is not None: - raise ValueError("Cannot provide bounds if image is provided") + raise GalSimIncompatibleValuesError( + "Cannot provide bounds if image is provided", bounds=bounds, image=image) if nx is not None or ny is not None: - raise ValueError("Cannot provide nx,ny if image is provided") + raise GalSimIncompatibleValuesError( + "Cannot provide nx,ny if image is provided", nx=nx, ny=ny, image=image) if dtype is not None and image.array.dtype != dtype: - raise ValueError("Cannot specify dtype != image.array.dtype if image is provided") + raise GalSimIncompatibleValuesError( + "Cannot specify dtype != image.array.dtype if image is provided", + dtype=dtype, image=image) # Make image if necessary if image is None: # Can't add to image if none is provided. if add_to_image: - raise ValueError("Cannot add_to_image if image is None") + raise GalSimIncompatibleValuesError( + "Cannot add_to_image if image is None", add_to_image=add_to_image, image=image) # Use bounds or nx,ny if provided if bounds is not None: if nx is not None or ny is not None: - raise ValueError("Cannot set both bounds and (nx, ny)") + raise GalSimIncompatibleValuesError( + "Cannot set both bounds and (nx, ny)", nx=nx, ny=ny, bounds=bounds) image = Image(bounds=bounds, dtype=dtype) elif nx is not None or ny is not None: if nx is None or ny is None: - raise ValueError("Must set either both or neither of nx, ny") + raise GalSimIncompatibleValuesError( + "Must set either both or neither of nx, ny", nx=nx, ny=ny) image = Image(nx, ny, dtype=dtype) else: N = self.getGoodImageSize(1.0) @@ -1043,7 +1051,9 @@ def _setup_image(self, image, nx, ny, bounds, add_to_image, dtype, odd=False): elif not image.bounds.isDefined(): # Can't add to image if need to resize if add_to_image: - raise ValueError("Cannot add_to_image if image bounds are not defined") + raise GalSimIncompatibleValuesError( + "Cannot add_to_image if image bounds are not defined", + add_to_image=add_to_image, image=image) N = self.getGoodImageSize(1.0) if odd: N += 1 bounds = _BoundsI(1,N,1,N) @@ -1063,7 +1073,9 @@ def _local_wcs(self, wcs, image, offset, use_true_center, new_bounds): else: bounds = image.bounds if not bounds.isDefined(): - raise ValueError("Cannot provide non-local wcs with automatically sized image") + raise GalSimIncompatibleValuesError( + "Cannot provide non-local wcs with automatically sized image", + wcs=wcs, image=image, bounds=new_bounds) elif use_true_center: obj_cen = bounds.true_center else: @@ -1115,7 +1127,8 @@ def _determine_wcs(self, scale, wcs, image, default_wcs=None): # Determine the correct wcs given the input scale, wcs and image. if wcs is not None: if scale is not None: - raise ValueError("Cannot provide both wcs and scale") + raise GalSimIncompatibleValuesError( + "Cannot provide both wcs and scale", wcs=wcs, scale=scale) if not isinstance(wcs, BaseWCS): raise TypeError("wcs must be a BaseWCS instance") if image is not None: image.wcs = None @@ -1517,21 +1530,34 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N # Some parameters are only relevant for method == 'phot' if method != 'phot' and sensor is None: if n_photons != 0.: - raise ValueError("n_photons is only relevant for method='phot'") + raise GalSimIncompatibleValuesError( + "n_photons is only relevant for method='phot'", + method=method, sensor=sensor, n_photons=n_photons) if rng is not None: - raise ValueError("rng is only relevant for method='phot'") + raise GalSimIncompatibleValuesError( + "rng is only relevant for method='phot'", + method=method, sensor=sensor, rng=rng) if max_extra_noise != 0.: - raise ValueError("max_extra_noise is only relevant for method='phot'") + raise GalSimIncompatibleValuesError( + "max_extra_noise is only relevant for method='phot'", + method=method, sensor=sensor, max_extra_noise=max_extra_noise) if poisson_flux is not None: - raise ValueError("poisson_flux is only relevant for method='phot'") + raise GalSimIncompatibleValuesError( + "poisson_flux is only relevant for method='phot'", + method=method, sensor=sensor, poisson_flux=poisson_flux) if surface_ops != (): - raise ValueError("surface_ops are only relevant for method='phot'") + raise GalSimIncompatibleValuesError( + "surface_ops are only relevant for method='phot'", + method=method, sensor=sensor, surface_ops=surface_ops) if save_photons: - raise ValueError("save_photons is only valid for method='phot'") + raise GalSimIncompatibleValuesError( + "save_photons is only valid for method='phot'", + method=method, sensor=sensor, save_photons=save_photons) else: # If we want to save photons, it doesn't make sense to limit the number per shoot call. if save_photons and maxN is not None: - raise ValueError("Setting maxN is incompatible with save_photons=True") + raise GalSimIncompatibleValuesError( + "Setting maxN is incompatible with save_photons=True") # Do any delayed computation needed by fft or real_space drawing. if method != 'phot': @@ -2233,7 +2259,9 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, # Can't both recenter a provided image and add to it. if recenter and image.center != PositionI(0,0) and add_to_image: - raise ValueError("Cannot recenter a non-centered image when add_to_image=True") + raise GalSimIncompatibleValuesError( + "Cannot recenter a non-centered image when add_to_image=True", + recenter=recenter, image=image, add_to_image=add_to_image) # Set the center to 0,0 if appropriate if recenter and image.center != PositionI(0,0): diff --git a/galsim/hsm.py b/galsim/hsm.py index 519c6d4337d..9c8aad708ed 100644 --- a/galsim/hsm.py +++ b/galsim/hsm.py @@ -63,7 +63,7 @@ from .bounds import BoundsI from .shear import Shear from .image import Image, ImageI, ImageF, ImageD -from .errors import GalSimError, GalSimValueError, GalSimHSMError +from .errors import GalSimError, GalSimValueError, GalSimHSMError, GalSimIncompatibleValuesError class ShapeData(object): @@ -458,7 +458,9 @@ def _convertMask(image, weight=None, badpix=None): else: # if weight image was supplied, check if it has the right bounds and is non-negative if weight.bounds != image.bounds: - raise ValueError("Weight image does not have same bounds as the input Image!") + raise GalSimIncompatibleValuesError( + "Weight image does not have same bounds as the input Image.", + weight=weight, image=image) # also make sure there are no negative values if np.any(weight.array < 0) == True: @@ -481,7 +483,9 @@ def _convertMask(image, weight=None, badpix=None): # image; also check bounds if badpix is not None: if badpix.bounds != image.bounds: - raise ValueError("Badpix image does not have the same bounds as the input Image!") + raise GalSimIncompatibleValuesError( + "Badpix image does not have the same bounds as the input Image.", + badpix=badpix, image=image) mask.array[badpix.array != 0] = 0 # if no pixels are used, raise an exception diff --git a/galsim/image.py b/galsim/image.py index e3bb1ba7e24..4d600037b64 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -28,7 +28,7 @@ from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities from .errors import GalSimError, GalSimBoundsError, GalSimValueError, GalSimImmutableError -from .errors import GalSimUndefinedBoundsError +from .errors import GalSimUndefinedBoundsError, GalSimIncompatibleValuesError # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -401,9 +401,11 @@ def _get_xmin_ymin(array, kwargs): if not isinstance(b, BoundsI): raise TypeError("bounds must be a galsim.BoundsI instance") if b.xmax-b.xmin+1 != array.shape[1]: - raise ValueError("Shape of array is inconsistent with provided bounds") + raise GalSimIncompatibleValuesError( + "Shape of array is inconsistent with provided bounds", array=array, bounds=b) if b.ymax-b.ymin+1 != array.shape[0]: - raise ValueError("Shape of array is inconsistent with provided bounds") + raise GalSimIncompatibleValuesError( + "Shape of array is inconsistent with provided bounds", array=array, bounds=b) if b.isDefined(): xmin = b.xmin ymin = b.ymin @@ -713,15 +715,23 @@ def wrap(self, bounds, hermitian=False): _galsim.wrapImage(self._image, bounds._b, False, False) elif hermitian == 'x': if self.bounds.xmin != 0: - raise ValueError("hermitian == 'x' requires self.bounds.xmin == 0") + raise GalSimIncompatibleValuesError( + "hermitian == 'x' requires self.bounds.xmin == 0", + hermitian=hermitian, bounds=self.bounds) if bounds.xmin != 0: - raise ValueError("hermitian == 'x' requires bounds.xmin == 0") + raise GalSimIncompatibleValuesError( + "hermitian == 'x' requires bounds.xmin == 0", + hermitian=hermitian, bounds=bounds) _galsim.wrapImage(self._image, bounds._b, True, False) elif hermitian == 'y': if self.bounds.ymin != 0: - raise ValueError("hermitian == 'y' requires self.bounds.ymin == 0") + raise GalSimIncompatibleValuesError( + "hermitian == 'y' requires self.bounds.ymin == 0", + hermitian=hermitian, bounds=self.bounds) if bounds.ymin != 0: - raise ValueError("hermitian == 'y' requires bounds.ymin == 0") + raise GalSimIncompatibleValuesError( + "hermitian == 'y' requires bounds.ymin == 0", + hermitian=hermitian, bounds=bounds) _galsim.wrapImage(self._image, bounds._b, False, True) else: raise GalSimValueError("Invalid value for hermitian", hermitian, (False, 'x', 'y')) @@ -965,8 +975,8 @@ def copyFrom(self, rhs): if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if self.bounds.numpyShape() != rhs.bounds.numpyShape(): - raise ValueError("Trying to copy images that are not the same shape (%s -> %s)"%( - rhs.bounds, self.bounds)) + raise GalSimIncompatibleValuesError( + "Trying to copy images that are not the same shape", self_image=self, rhs=rhs) self._array[:,:] = rhs.array[:,:] def view(self, scale=None, wcs=None, origin=None, center=None, make_const=False): @@ -1584,7 +1594,7 @@ def check_image_consistency(im1, im2, integer=False): raise GalSimValueError("Image must have integer values.",im1) if isinstance(im2, Image): if im1.array.shape != im2.array.shape: - raise ValueError("Image shapes are inconsistent") + raise GalSimIncompatibleValuesError( "Image shapes are inconsistent", im1=im1, im2=im2) if integer and not im2.isinteger: raise GalSimValueError("Image must have integer values.",im2) diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 31573fdea27..5d2ffe8e9b0 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -35,7 +35,7 @@ from . import _galsim from . import fits from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError -from .errors import GalSimWarning +from .errors import GalSimIncompatibleValuesError, GalSimWarning class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -324,7 +324,9 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= raise TypeError("wcs parameter is not a galsim.BaseWCS instance") self._image.wcs = wcs elif self._image.wcs is None: - raise ValueError("No information given with Image or keywords about pixel scale!") + raise GalSimIncompatibleValuesError( + "No information given with Image or keywords about pixel scale!", + scale=scale, wcs=wcs, image=image) # Figure out the offset to apply based on the original image (not the padded one). # We will apply this below in _sbp. @@ -833,7 +835,9 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, real_kimage=None, imag_kimage=None, real_hdu=None, imag_hdu=None): if kimage is None: if real_kimage is None or imag_kimage is None: - raise ValueError("Must provide either kimage or real_kimage/imag_kimage") + raise GalSimIncompatibleValuesError( + "Must provide either kimage or real_kimage/imag_kimage", + kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimage) # If the "image" is not actually an image, try to read the image as a file. if not isinstance(real_kimage, Image): @@ -848,14 +852,20 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, if not isinstance(imag_kimage, Image): raise GalSimValueError("Supplied imag_kimage is not an Image instance", imag_kimage) if real_kimage.bounds != imag_kimage.bounds: - raise ValueError("Real and Imag kimages must have same bounds.") + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must have same bounds.", + real_kimage=real_kimage, imag_kimage=imag_kimage) if real_kimage.wcs != imag_kimage.wcs: - raise ValueError("Real and Imag kimages must have same scale/wcs.") + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must have same scale/wcs.", + real_kimage=real_kimage, imag_kimage=imag_kimage) kimage = real_kimage + 1j*imag_kimage else: if real_kimage is not None or imag_kimage is not None: - raise ValueError("Cannot provide both kimage and real_kimage/imag_kimage") + raise GalSimIncompatibleValuesError( + "Cannot provide both kimage and real_kimage/imag_kimage", + kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimag) if not kimage.iscomplex: raise GalSimValueError("Supplied kimage is not complex", kimage) @@ -878,7 +888,8 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, kimage[bd].real.array[::-1,::-1]) and np.allclose(kimage[bd].imag.array, -kimage[bd].imag.array[::-1,::-1])): - raise ValueError("Real and Imag kimages must form a Hermitian complex matrix.") + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must form a Hermitian complex matrix.", kimage=kimage) if stepk is None: if self._kimage.scale is None: diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index 10e9dbf8489..acb7291b8ad 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -32,7 +32,7 @@ from . import utilities from . import integ from . import _galsim -from .errors import GalSimError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -170,8 +170,9 @@ class PowerSpectrum(object): def __init__(self, e_power_function=None, b_power_function=None, delta2=False, units=arcsec): # Check that at least one power function is not None if e_power_function is None and b_power_function is None: - raise ValueError( - "At least one of e_power_function or b_power_function must be provided.") + raise GalSimIncompatibleValuesError( + "At least one of e_power_function or b_power_function must be provided.", + e_power_function=e_power_function, b_power_function=b_power_function) self.e_power_function = e_power_function self.b_power_function = b_power_function @@ -1539,7 +1540,7 @@ def kappaKaiserSquires(g1, g2): if not isinstance(g1, np.ndarray) and isinstance(g2, np.ndarray): raise TypeError("Input g1 and g2 must be galsim Image or NumPy arrays.") if g1.shape != g2.shape: - raise ValueError("Input g1 and g2 must be the same shape.") + raise GalSimIncompatibleValuesError("Input g1 and g2 must be the same shape.", g1=g1, g2=g2) if g1.shape[0] != g1.shape[1]: raise NotImplementedError("Non-square input shear grids not supported.") diff --git a/galsim/noise.py b/galsim/noise.py index b83d51b0e60..c7ee6537fa4 100644 --- a/galsim/noise.py +++ b/galsim/noise.py @@ -25,7 +25,8 @@ import math from .image import Image, ImageD -from .errors import GalSimError +from .utilities import doc_inherit +from .errors import GalSimError, GalSimIncompatibleValuesError def addNoise(self, noise): @@ -620,13 +621,15 @@ def var_image(self): # Repeat this here, since we want to add an extra sanity check, which should go in the # non-underscore version. + @doc_inherit def applyTo(self, image): if not isinstance(image, Image): raise TypeError("Provided image must be a galsim.Image") if image.array.shape != self.var_image.array.shape: - raise ValueError("Provided image shape does not match the shape of var_image") + raise GalSimIncompatibleValuesError( + "Provided image shape does not match the shape of var_image", + image=image, var_image=self.var_image) return self._applyTo(image) - applyTo.__doc__ = BaseNoise.applyTo.__doc__ def _applyTo(self, image): noise_array = self.var_image.array.flatten() # NB. Makes a copy! (which is what we want) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 0d1473f6a04..d613b750697 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,7 +80,7 @@ from .wcs import PixelScale from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property -from .errors import GalSimError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -233,10 +233,15 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, strut_thick == 0.05 and strut_angle == 0.0*radians) if not is_default_geom and pupil_plane_im is not None: - raise ValueError("Can't specify both geometric parameters and pupil_plane_im.") + raise GalSimIncompatibleValuesError( + "Can't specify both geometric parameters and pupil_plane_im.", + circular_pupil=circular_pupil, nstruts=nstruts, strut_thick=strut_thick, + strut_angle=strut_angle, pupil_plane_im=pupil_plane_im) if screen_list is not None and lam is None: - raise ValueError("Wavelength `lam` must be specified with `screen_list`.") + raise GalSimIncompatibleValuesError( + "Wavelength `lam` must be specified with `screen_list`.", + screen_list=screen_list, lam=lam) # Although the user can set the pupil plane size and scale directly if desired, in most # cases it's nicer to have GalSim try to pick good values for these. @@ -1072,7 +1077,7 @@ class PhaseScreenPSF(GSObject): The following are optional keywords to use to setup the aperture if `aper` is not provided: - @param diam Aperture diameter in meters. + @param diam Aperture diameter in meters. [default: None] @param circular_pupil Adopt a circular pupil? [default: True] @param obscuration Linear dimension of central obscuration as fraction of aperture linear dimension. [0., 1.). [default: 0.0] @@ -1128,7 +1133,8 @@ def __init__(self, screen_list, lam, t0=0.0, exptime=0.0, time_step=0.025, flux= if aper is None: # Check here for diameter. if 'diam' not in kwargs: - raise ValueError("Diameter required if aperture not specified directly.") + raise GalSimIncompatibleValuesError( + "Diameter required if aperture not specified directly.", diam=None, aper=aper) aper = Aperture(lam=lam, screen_list=self._screen_list, gsparams=gsparams, **kwargs) self.aper = aper diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index d19e70aef32..08bf505b6ce 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -26,7 +26,7 @@ from . import utilities from . import fft from . import zernike -from .errors import GalSimRangeError, GalSimValueError, GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning class AtmosphericScreen(object): @@ -107,10 +107,13 @@ def __init__(self, screen_size, screen_scale=None, altitude=0.0, r0_500=0.2, L0= vx=0.0, vy=0.0, alpha=1.0, time_step=None, rng=None, suppress_warning=False): if (alpha != 1.0 and time_step is None): - raise ValueError("No time_step provided when alpha != 1.0") + raise GalSimIncompatibleValuesError( + "No time_step provided when alpha != 1.0", alpha=alpha, time_step=time_step) if (alpha == 1.0 and time_step is not None): - raise ValueError("Setting AtmosphericScreen time_step prohibited when alpha == 1.0. " - "Did you mean to set time_step in makePSF or PhaseScreenPSF?") + raise GalSimIncompatibleValuesError( + "Setting AtmosphericScreen time_step prohibited when alpha == 1.0. " + "Did you mean to set time_step in makePSF or PhaseScreenPSF?", + alpha=alpha, time_step=time_step) if screen_scale is None: # We copy Jee+Tyson(2011) and (arbitrarily) set the screen scale equal to r0 by default. screen_scale = r0_500 @@ -339,7 +342,7 @@ def wavefront(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): u = np.array(u, dtype=float) v = np.array(v, dtype=float) if u.shape != v.shape: - raise ValueError("u.shape not equal to v.shape") + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape",u=u,v=v) if t is None: t = self._time @@ -352,7 +355,8 @@ def wavefront(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): else: t = np.array(t, dtype=float) if t.shape != u.shape: - raise ValueError("t.shape must match u.shape if t is not a scalar") + raise GalSimIncompatibleValuesError( + "t.shape must match u.shape if t is not a scalar", t=t, u=u) self.instantiate() # noop if already instantiated @@ -399,7 +403,7 @@ def wavefront_gradient(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): u = np.array(u, dtype=float) v = np.array(v, dtype=float) if u.shape != v.shape: - raise ValueError("u.shape not equal to v.shape") + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) from numbers import Real if isinstance(t, Real): @@ -409,7 +413,8 @@ def wavefront_gradient(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): else: t = np.array(t, dtype=float) if t.shape != u.shape: - raise ValueError("t.shape must match u.shape if t is not a scalar") + raise GalSimIncompatibleValuesError( + "t.shape must match u.shape if t is not a scalar", t=t, u=u) self.instantiate() # noop if already instantiated @@ -581,7 +586,9 @@ def Atmosphere(screen_size, rng=None, _bar=None, **kwargs): kwargs['r0_500'] = [r0_500 * w**(-3./5) for w in r0_weights] # kwargs['r0_500'] = [nmax**(3./5) * kwargs['r0_500'][0]] * nmax elif 'r0_weights' in kwargs: - raise ValueError("Cannot use r0_weights if r0_500 is specified as a list.") + raise GalSimIncompatibleValuesError( + "Cannot use r0_weights if r0_500 is specified as a list.", + r0_weights=kwargs['r0_weights'], r0_500=kwargs['r0_500']) if rng is None: rng = BaseDeviate() @@ -745,7 +752,7 @@ def wavefront(self, u, v, t=None, theta=None): u = np.array(u, dtype=float) v = np.array(v, dtype=float) if u.shape != v.shape: - raise ValueError("u.shape not equal to v.shape") + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) return self._wavefront(u, v, t, theta) def _wavefront(self, u, v, t, theta): @@ -767,7 +774,7 @@ def wavefront_gradient(self, u, v, t=None, theta=None): u = np.array(u, dtype=float) v = np.array(v, dtype=float) if u.shape != v.shape: - raise ValueError("u.shape not equal to v.shape") + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) return self._wavefront_gradient(u, v, t, theta) diff --git a/galsim/pse.py b/galsim/pse.py index 1d71bcbed08..599433dd46c 100644 --- a/galsim/pse.py +++ b/galsim/pse.py @@ -27,7 +27,8 @@ import os import sys -from .errors import GalSimError, GalSimValueError +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError + class PowerSpectrumEstimator(object): """ @@ -208,7 +209,8 @@ def estimate(self, g1, g2, weight_EE=False, weight_BB=False, theory_func=None): from .table import LookupTable # Check for the expected square geometry consistent with the previously-defined grid size. if g1.shape != g2.shape: - raise ValueError("g1 and g2 grids do not have the same shape!") + raise GalSimIncompatibleValuesError( + "g1 and g2 grids do not have the same shape.", g1=g1, g2=g2) if g1.shape[0] != g1.shape[1]: raise GalSimValueError("Input shear arrays must be square.", g1.shape) if g1.shape[0] != self.N: diff --git a/galsim/real.py b/galsim/real.py index 569bdbf4353..408da542fb1 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -39,7 +39,8 @@ from .chromatic import ChromaticSum from .position import PositionD from .utilities import doc_inherit -from .errors import GalSimError, GalSimValueError +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError + HST_area = 45238.93416 # Area of HST primary mirror in cm^2 from Synphot User's Guide. @@ -220,11 +221,14 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, # Get the index to use in the catalog if index is not None: if id is not None or random: - raise ValueError('Too many methods for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", + index=index, id=id, random=random) use_index = index elif id is not None: if random: - raise ValueError('Too many methods for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", id=id, random=random) use_index = real_galaxy_catalog.getIndexForID(id) elif random: ud = UniformDeviate(self.rng) @@ -237,7 +241,9 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, # Pick another one to try. use_index = int(real_galaxy_catalog.nobjects * ud()) else: - raise ValueError('No method specified for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "No method specified for selecting a galaxy.", + index=index, id=id, random=random) if logger: logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) @@ -529,7 +535,9 @@ class RealGalaxyCatalog(object): def __init__(self, file_name=None, sample=None, dir=None, preload=False, logger=None, _nobjects_only=False): if sample is not None and file_name is not None: - raise ValueError("Cannot specify both the sample and file_name!") + raise GalSimIncompatibleValuesError( + "Cannot specify both the sample and file_name.", + sample=sample, file_name=file_name) from ._pyfits import pyfits self.file_name, self.image_dir, _ = _parse_files_dirs(file_name, dir, sample) @@ -1016,17 +1024,20 @@ def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng= # Get the index to use in the catalog if index is not None: if id is not None or random: - raise ValueError('Too many methods for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", index=index, id=id, random=random) use_index = index elif id is not None: if random: - raise ValueError('Too many methods for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", id=id, random=random) use_index = real_galaxy_catalogs[0].getIndexForID(id) elif random: uniform_deviate = UniformDeviate(self.rng) use_index = int(real_galaxy_catalogs[0].nobjects * uniform_deviate()) else: - raise ValueError('No method specified for selecting a galaxy!') + raise GalSimIncompatibleValuesError( + "No method specified for selecting a galaxy.", index=index, id=id, random=random) if logger: logger.debug('ChromaticRealGalaxy %d: Start ChromaticRealGalaxy constructor.', use_index) diff --git a/galsim/scene.py b/galsim/scene.py index 838a66bb5bf..84115008a18 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -26,7 +26,7 @@ import os from .real import RealGalaxy, RealGalaxyCatalog -from .errors import GalSimError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -166,7 +166,9 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, use_real=True, exclusion_level='marginal', min_hlr=0, max_hlr=0., min_flux=0., max_flux=0., _nobjects_only=False): if sample is not None and file_name is not None: - raise ValueError("Cannot specify both the sample and file_name!") + raise GalSimIncompatibleValuesError( + "Cannot specify both the sample and file_name.", + sample=sample, file_name=file_name) from ._pyfits import pyfits from .real import _parse_files_dirs @@ -479,7 +481,9 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= if gal_type is None: gal_type = 'parametric' elif gal_type != 'parametric': - raise ValueError("Only 'parametric' galaxy type is allowed when use_real == False") + raise GalSimIncompatibleValuesError( + "Only 'parametric' galaxy type is allowed when use_real == False", + gal_type=gal_type, use_real=self.use_real) else: if gal_type is None: gal_type = 'real' @@ -872,7 +876,9 @@ def _makeSingleGalaxy(cosmos_catalog, index, gal_type, noise_pad_size=5, deep=Fa if gal_type is None: gal_type = 'parametric' elif gal_type != 'parametric': - raise ValueError("Only 'parametric' galaxy type is allowed when use_real == False") + raise GalSimIncompatibleValuesError( + "Only 'parametric' galaxy type is allowed when use_real == False", + gal_type=gal_type, use_real=False) if gal_type not in ('real', 'parametric'): raise GalSimValueError("Invalid galaxy type", gal_type, ('real', 'parametric')) diff --git a/galsim/sed.py b/galsim/sed.py index 2a67de14c12..1f400f96206 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -32,6 +32,7 @@ from . import dcr from .utilities import WeakMethod, lazy_property, combine_wave_list from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimSEDError +from .errors import GalSimIncompatibleValuesError class SED(object): """Object to represent the spectral energy distributions of stars and galaxies. @@ -607,7 +608,8 @@ def __add__(self, other): # These conditions ensure that SED addition is commutative. if self.redshift != other.redshift: - raise ValueError("Can only add SEDs with same redshift.") + raise GalSimIncompatibleValuesError( + "Can only add SEDs with same redshift.", self_sed=self, other=other) if self.dimensionless and other.dimensionless: flux_type = '1' diff --git a/galsim/shapelet.py b/galsim/shapelet.py index 1a2689a353a..c7cc7aa10a6 100644 --- a/galsim/shapelet.py +++ b/galsim/shapelet.py @@ -28,7 +28,7 @@ from .image import Image from .utilities import doc_inherit from . import _galsim -from .errors import GalSimValueError +from .errors import GalSimValueError, GalSimIncompatibleValuesError class Shapelet(GSObject): @@ -136,7 +136,8 @@ def __init__(self, sigma, order, bvec=None, gsparams=None): self._bvec = np.empty(bvec_size, dtype=float) else: if len(bvec) != bvec_size: - raise ValueError("bvec is the wrong size for the provided order") + raise GalSimIncompatibleValuesError( + "bvec is the wrong size for the provided order", bvec=bvec, order=order) self._bvec = np.ascontiguousarray(bvec, dtype=float) self._sbp = _galsim.SBShapelet(self._sigma, self._order, self._bvec.ctypes.data, diff --git a/galsim/table.py b/galsim/table.py index 35e50acddbd..4383b8d93b8 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -29,6 +29,7 @@ from .position import PositionD from .bounds import BoundsD from .errors import GalSimRangeError, GalSimBoundsError, GalSimValueError +from .errors import GalSimIncompatibleValuesError class LookupTable(object): """ @@ -105,7 +106,7 @@ def __init__(self, x=None, f=None, interpolant=None, x_log=False, f_log=False): # Sanity checks if len(x) != len(f): - raise ValueError("Input array lengths don't match") + raise GalSimIncompatibleValuesError("Input array lengths don't match", x=x, f=f) if interpolant == 'spline' and len(x) < 3: raise GalSimValueError("Input arrays too small to spline interpolate", x) if interpolant in ['linear', 'ceil', 'floor', 'nearest'] and len(x) < 2: @@ -438,7 +439,8 @@ def __init__(self, x, y, f, interpolant='linear', edge_mode='raise', constant=0) fshape = self.f.shape if fshape != (len(x), len(y)): - raise ValueError("Shape of `f` must be (len(`x`), len(`y`)).") + raise GalSimIncompatibleValuesError( + "Shape of f incompatible with lengths of x,y", f=f, x=x, y=y) if interpolant not in ('linear', 'ceil', 'floor', 'nearest'): raise GalSimValueError("Unknown interpolant.", interpolant, @@ -461,8 +463,10 @@ def __init__(self, x, y, f, interpolant='linear', edge_mode='raise', constant=0) self.xperiod = self.x[-1] - self.x[0] self.yperiod = self.y[-1] - self.y[0] else: - raise ValueError("Cannot use edge_mode='wrap' unless either x and y are equally " - "spaced or first/last row/column of f are identical.") + raise GalSimIncompatibleValuesError( + "Cannot use edge_mode='wrap' unless either x and y are equally " + "spaced or first/last row/column of f are identical.", + edge_mode=edge_mode, x=x, y=y, f=f) @lazy_property def _tab(self): diff --git a/galsim/utilities.py b/galsim/utilities.py index b3ea4374240..c3a6b65ebe2 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -25,7 +25,7 @@ import weakref import numpy as np -from .errors import GalSimError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning def roll2d(image, shape): @@ -392,7 +392,7 @@ def thin_tabulated_values(x, f, rel_err=1.e-4, trim_zeros=True, preserve_range=T # Check for valid inputs if len(x) != len(f): - raise ValueError("len(x) != len(f)") + raise GalSimIncompatibleValuesError("len(x) != len(f)", x=x, f=f) if rel_err <= 0 or rel_err >= 1: raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): @@ -490,7 +490,7 @@ def old_thin_tabulated_values(x, f, rel_err=1.e-4, preserve_range=False): # prag # Check for valid inputs if len(x) != len(f): - raise ValueError("len(x) != len(f)") + raise GalSimIncompatibleValuesError("len(x) != len(f)", x=x, f=f) if rel_err <= 0 or rel_err >= 1: raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) if not (np.diff(x) >= 0).all(): @@ -716,8 +716,9 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): y_size,x_size = image.array.shape if x_size%n1 or y_size%n2: - raise ValueError("The value of 'N' is incompatible with the dimensions of the image to "+ - +"be 'deinterleaved'") + raise GalSimIncompatibleValuesError( + "The value of N is incompatible with the dimensions of the image to be deinterleaved", + N=N, image=image) im_list, offsets = [], [] for i in range(n1): @@ -852,10 +853,12 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False raise TypeError("'im_list' needs to have at least two instances of galsim.Image") if (n1*n2 != len(im_list)): - raise ValueError("'N' is incompatible with the number of images in 'im_list'") + raise GalSimIncompatibleValuesError( + "N is incompatible with the number of images in im_list", N=N, im_list=im_list) if len(im_list)!=len(offsets): - raise ValueError("'im_list' and 'offsets' must be lists of same length") + raise GalSimIncompatibleValuesError( + "im_list and offsets must be lists of same length", im_list=im_list, offsets=offsets) for offset in offsets: if not isinstance(offset, PositionD): @@ -873,11 +876,12 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False raise TypeError("'im_list' must be a list of galsim.Image instances") if im.array.shape != (y_size,x_size): - raise ValueError("All galsim.Image instances in 'im_list' must be of the same size") + raise GalSimIncompatibleValuesError( + "All galsim.Image instances in im_list must be of the same size", im_list=im_list) if im.wcs != wcs: - raise ValueError( - "All galsim.Image instances in 'im_list' must have the same WCS") + raise GalSimIncompatibleValuesError( + "All galsim.Image instances in im_list must have the same WCS", im_list=im_list) img_array = np.zeros((n2*y_size,n1*x_size)) # The tricky part - going from (x,y) Image coordinates to array indices @@ -894,14 +898,16 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False err_j = (n2-1)*0.5-n2*dy - round((n2-1)*0.5-n2*dy) tol = 1.e-6 if abs(err_i)>tol or abs(err_j)>tol: - raise ValueError( - "'offsets' must be a list of galsim.PositionD instances with x values "+ - "spaced by 1/{0} and y values by 1/{1} around 0 for N = ".format(n1,n2)+str(N)) + raise GalSimIncompatibleValuesError( + "offsets must be a list of galsim.PositionD instances with x values " + "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), + N=N, offsets=offsets) if i<0 or j<0 or i>=x_size or j>=y_size: - raise ValueError( - "'offsets' must be a list of galsim.PositionD instances with x values "+ - "spaced by 1/{0} and y values by 1/{1} around 0 for N = ".format(n1,n2)+str(N)) + raise GalSimIncompatibleValuesError( + "offsets must be a list of galsim.PositionD instances with x values " + "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), + N=N, offsets=offsets) img_array[j::n2,i::n1] = im_list[k].array[:,:] @@ -1071,14 +1077,16 @@ def dol_to_lod(dol, N=None): out[k] = v[i] except IndexError: # It's list-like, but too short. if len(v) != 1: - raise ValueError("Cannot broadcast kwargs of different non-length-1 lengths.") + raise GalSimIncompatibleValuesError( + "Cannot broadcast kwargs of different non-length-1 lengths.", dol=dol) out[k] = v[0] except TypeError: # Value is not list-like, so broadcast it in its entirety. out[k] = v except KeyboardInterrupt: raise except: # pragma: no cover - raise ValueError("Cannot broadcast kwarg {0}={1}".format(k, v)) + raise GalSimIncompatibleValuesError( + "Cannot broadcast kwarg {0}={1}".format(k, v), dol=dol) yield out def structure_function(image): @@ -1341,10 +1349,10 @@ def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): if weight is not None: # We need some sanity checks here in case people passed in weird values. if len(weight) != n_choices: - raise ValueError("Array of weights has wrong length: %d instead of %d"% - (len(weight), n_choices)) - if np.min(weight)<0 or np.max(weight)>1 or np.any(np.isnan(weight)) or \ - np.any(np.isinf(weight)): + raise GalSimIncompatibleValuesError( + "Array of weights has wrong length", weight=weight, n_choices=n_choices) + if (np.min(weight)<0 or np.max(weight)>1 or np.any(np.isnan(weight)) or + np.any(np.isinf(weight))): raise GalSimValueError("Supplied weights include values outside [0,1] or inf/NaN.", weight) From e9703f24e01cba374d4f1f1ee076aa9972212d25 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 10:08:14 -0400 Subject: [PATCH 12/96] Convert some TypeErrors into one of our new error classes (#755) --- galsim/airy.py | 13 ++++--- galsim/bandpass.py | 14 ++++---- galsim/bounds.py | 8 ++--- galsim/chromatic.py | 42 ++++++++++++---------- galsim/correlatednoise.py | 10 +++--- galsim/dcr.py | 17 +++++---- galsim/download_cosmos.py | 6 ++-- galsim/errors.py | 4 +-- galsim/exponential.py | 13 +++---- galsim/fits.py | 69 ++++++++++++++++++++++--------------- galsim/fitswcs.py | 67 +++++++++++++++++++++++------------ galsim/gaussian.py | 19 +++++----- galsim/gsobject.py | 19 +++++----- galsim/image.py | 29 ++++++++++------ galsim/inclined.py | 52 ++++++++++++++-------------- galsim/integ.py | 2 +- galsim/interpolant.py | 2 +- galsim/interpolatedimage.py | 5 +-- galsim/kolmogorov.py | 29 ++++++++++------ galsim/lensing_ps.py | 30 ++++++++-------- galsim/main.py | 26 +++++++------- galsim/moffat.py | 20 +++++------ galsim/nfw_halo.py | 6 ++-- galsim/phase_psf.py | 38 ++++++++++++-------- galsim/phase_screens.py | 8 +++-- galsim/photon_array.py | 5 +-- galsim/position.py | 6 ++-- galsim/random.py | 28 +++++++++------ galsim/randwalk.py | 3 +- galsim/real.py | 15 ++++---- galsim/scene.py | 44 +++++++++++------------ galsim/sed.py | 28 ++++++++------- galsim/sersic.py | 13 +++---- galsim/shapelet.py | 4 +-- galsim/shear.py | 38 ++++++++++---------- galsim/spergel.py | 13 +++---- galsim/table.py | 5 +-- galsim/utilities.py | 32 ++++++++--------- galsim/wcs.py | 6 ++-- tests/test_chromatic.py | 6 ++-- tests/test_sed.py | 2 +- 41 files changed, 448 insertions(+), 348 deletions(-) diff --git a/galsim/airy.py b/galsim/airy.py index 874c95eff48..26b50f30296 100644 --- a/galsim/airy.py +++ b/galsim/airy.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimIncompatibleValuesError class Airy(GSObject): @@ -127,11 +128,15 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, obscuration=0., flux # docstring. if lam_over_diam is not None: if lam is not None or diam is not None: - raise TypeError("If specifying lam_over_diam, then do not specify lam or diam") + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify lam or diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) self._lod = float(lam_over_diam) else: if lam is None or diam is None: - raise TypeError("If not specifying lam_over_diam, then specify lam AND diam") + raise GalSimIncompatibleValuesError( + "If not specifying lam_over_diam, then specify lam AND diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) # In this case we're going to use scale_unit, so parse it in case of string input: if isinstance(scale_unit, str): scale_unit = AngleUnit.from_name(scale_unit) @@ -158,7 +163,7 @@ def half_light_radius(self): else: # In principle can find the half light radius as a function of lam_over_diam and # obscuration too, but it will be much more involved...! - raise NotImplementedError("Half light radius calculation not implemented for Airy "+ + raise NotImplementedError("Half light radius calculation not implemented for Airy " "objects with non-zero obscuration.") @property @@ -171,7 +176,7 @@ def fwhm(self): else: # In principle can find the FWHM as a function of lam_over_diam and obscuration too, # but it will be much more involved...! - raise NotImplementedError("FWHM calculation not implemented for Airy "+ + raise NotImplementedError("FWHM calculation not implemented for Airy " "objects with non-zero obscuration.") def __eq__(self, other): diff --git a/galsim/bandpass.py b/galsim/bandpass.py index 171409c1749..6c1ca449039 100644 --- a/galsim/bandpass.py +++ b/galsim/bandpass.py @@ -169,8 +169,9 @@ def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, self.red_limit = float(self._tp.x_max)/self.wave_factor else: if self.blue_limit is None or self.red_limit is None: - raise TypeError( - "red_limit and blue_limit are required if throughput is not a LookupTable.") + raise GalSimIncompatibleValuesError( + "red_limit and blue_limit are required if throughput is not a LookupTable.", + blue_limit=blue_limit, red_limit=red_limit, throughput=throughput) # Sanity check blue/red limit and create self.wave_list if isinstance(self._tp, LookupTable): @@ -219,15 +220,16 @@ def _initialize_tp(self): self._tp = LookupTable.from_file(filename, interpolant='linear') else: if self.blue_limit is None or self.red_limit is None: - raise TypeError( - "red_limit and blue_limit are required if throughput is not a LookupTable.") + raise GalSimIncompatibleValuesError( + "red_limit and blue_limit are required if throughput is not a LookupTable.", + blue_limit=None, red_limit=None, throughput=self._orig_tp) test_wave = self.blue_limit try: self._tp = utilities.math_eval('lambda wave : ' + self._orig_tp) test_value = self._tp(test_wave) except Exception as e: raise GalSimValueError( - "String throughput must either be a valid filename or something that "+ + "String throughput must either be a valid filename or something that " "can eval to a function of wave.\n Caught error: %s."%(e), self._orig_tp) from numbers import Real @@ -597,7 +599,7 @@ def __hash__(self): return self._hash def __repr__(self): - return ('galsim.Bandpass(%r, wave_type=%r, blue_limit=%r, red_limit=%r, zeropoint=%r, '+ + return ('galsim.Bandpass(%r, wave_type=%r, blue_limit=%r, red_limit=%r, zeropoint=%r, ' '_wave_list=array(%r))')%( self._orig_tp, self.wave_type, self.blue_limit, self.red_limit, self.zeropoint, self.wave_list.tolist()) diff --git a/galsim/bounds.py b/galsim/bounds.py index 62974db2b71..47aa7dfd7f8 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -94,7 +94,7 @@ class Bounds(object): information. """ def __init__(self): - raise NotImplementedError("Cannot instantiate the base class. " + + raise NotImplementedError("Cannot instantiate the base class. " "Use either BoundsD or BoundsI.") def _parse_args(self, *args, **kwargs): @@ -136,7 +136,7 @@ def _parse_args(self, *args, **kwargs): self.__class__.__name__,len(args))) elif len(args) != 0: raise TypeError("Cannot provide both keywork and non-keyword arguments to %s"%( - self.__class__.__name__)) + self.__class__.__name__)) else: try: self._isdefined = True @@ -146,7 +146,7 @@ def _parse_args(self, *args, **kwargs): self.ymax = kwargs.pop('ymax') except KeyError: raise TypeError("Keyword arguments, xmin, xmax, ymin, ymax are required for %s"%( - self.__class__.__name__)) + self.__class__.__name__)) if kwargs: raise TypeError("Got unexpected keyword arguments %s"%kwargs.keys()) @@ -332,7 +332,7 @@ def __add__(self, other): return self.__class__(other) else: raise TypeError("other must be either a %s or a %s"%( - self.__class__.__name__,self._pos_class.__name__)) + self.__class__.__name__,self._pos_class.__name__)) def __repr__(self): if self.isDefined(): diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 0c7d9a1c61c..a618987c7fe 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -35,8 +35,8 @@ from .utilities import lazy_property from . import utilities from . import integ -from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimIncompatibleValuesError -from .errors import GalSimWarning +from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimValueError +from .errors import GalSimIncompatibleValuesError, GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -332,7 +332,8 @@ def _get_integrator(integrator, wave_list): elif integrator == 'midpoint': rule = integ.midptRule else: - raise TypeError("Unrecognized integration rule: %s"%integrator) + raise GalSimValueError("Unrecognized integration rule", integrator, + ('trapezoidal', 'midpoint')) if len(wave_list) > 0: integrator = integ.SampleIntegrator(rule) else: @@ -609,7 +610,7 @@ def withFluxDensity(self, target_flux_density, wavelength): _photons = units.astrophys.photon/(units.s * units.cm**2 * units.nm) if self.dimensionless: - raise TypeError("Cannot set flux density of dimensionless ChromaticObject.") + raise GalSimSEDError("Cannot set flux density of dimensionless ChromaticObject.", self) if isinstance(wavelength, units.Quantity): wavelength_nm = wavelength.to(units.nm, units.spectral()) current_flux_density = self.SED(wavelength_nm.value) @@ -1138,11 +1139,11 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', instead interact with the `drawImage` method. """ from .interpolatedimage import InterpolatedImage - if integrator not in ['trapezoidal', 'midpoint']: + if integrator not in ('trapezoidal', 'midpoint'): if not isinstance(integrator, str): raise TypeError("Integrator should be a string indicating trapezoidal" " or midpoint rule for integration") - raise TypeError("Unknown integrator: %s"%integrator) + raise GalSimValueError("Unknown integrator",integrator, ('trapezoidal', 'midpoint')) if _flux_ratio is None: _flux_ratio = lambda w: 1.0 @@ -1338,7 +1339,7 @@ def __init__(self, base_obj, base_wavelength, scale_unit=None, **kwargs): # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction # Check that they're valid for kw in self.kw: - if kw not in ['temperature', 'pressure', 'H2O_pressure']: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): raise TypeError("Got unexpected keyword: {0}".format(kw)) self.base_refraction = dcr.get_refraction(self.base_wavelength, self.zenith_angle, @@ -1679,7 +1680,7 @@ def noise(self): if (hasattr(self._jac, '__call__') or hasattr(self._offset, '__call__') or not self._flux_ratio._const): - raise TypeError("Cannot propagate noise through chromatic transformation") + raise GalSimError("Cannot propagate noise through chromatic transformation") noise = self.original.noise jac = self._jac flux_ratio = self._flux_ratio(42.) # const, so use any wavelength @@ -1736,7 +1737,7 @@ def __init__(self, *args, **kwargs): args = args[0] else: raise TypeError("Single input argument must be a GSObject, a ChromaticObject," - +" or list of them.") + " or list of them.") # else args is already the list of objects self.interpolated = any(arg.interpolated for arg in args) @@ -1913,9 +1914,8 @@ def __init__(self, *args, **kwargs): elif isinstance(args[0], list): args = args[0] else: - raise TypeError( - "Single input argument must be a GSObject, or a ChromaticObject," - +" or list of them.") + raise TypeError("Single input argument must be a GSObject, or a ChromaticObject," + " or list of them.") # else args is already the list of objects # Check kwargs @@ -1969,7 +1969,7 @@ def __init__(self, *args, **kwargs): if n_nonsep>1 and n_interp>0: # pragma: no cover import warnings warnings.warn( - "Image rendering for this convolution cannot take advantage of " + + "Image rendering for this convolution cannot take advantage of " "interpolation-related optimization. Will use full profile evaluation.", GalSimWarning) @@ -2195,11 +2195,11 @@ def noise(self): # Exactly one of the convolutants has a .covspec attribute. covspecs = [ obj.covspec for obj in self.obj_list if hasattr(obj, 'covspec') ] if len(covspecs) != 1: - raise TypeError("Cannot compute noise for ChromaticConvolution for which number " - "of convolutants with covspec attribute is not 1.") + raise GalSimError("Cannot compute noise for ChromaticConvolution for which number " + "of convolutants with covspec attribute is not 1.") if not hasattr(self, '_last_bp'): - raise TypeError("Cannot compute noise for ChromaticConvolution until after drawImage " - "has been called.") + raise GalSimError("Cannot compute noise for ChromaticConvolution until after drawImage " + "has been called.") covspec = covspecs[0] other = Convolve([obj for obj in self.obj_list if not hasattr(obj, 'covspec')]) return covspec.toNoise(self._last_bp, other, self._last_wcs) # rng=? @@ -2497,7 +2497,9 @@ def __init__(self, lam, diam=None, lam_over_diam=None, aberrations=None, # We have to require either diam OR lam_over_diam: if (diam is None and lam_over_diam is None) or \ (diam is not None and lam_over_diam is not None): - raise TypeError("Need to specify telescope diameter OR wavelength/diam ratio") + raise GalSimIncompatibleValuesError( + "Need to specify telescope diameter OR wavelength/diam ratio", + diam=diam, lam_over_diam=lam_over_diam) if diam is not None: self.lam_over_diam = (1.e-9*lam/diam)*radians/self.scale_unit self.diam = diam @@ -2607,7 +2609,9 @@ def __init__(self, lam, diam=None, lam_over_diam=None, scale_unit=None, **kwargs if (diam is None and lam_over_diam is None) or \ (diam is not None and lam_over_diam is not None): - raise TypeError("Need to specify telescope diameter OR wavelength/diam ratio") + raise GalSimIncompatibleValuesError( + "Need to specify telescope diameter OR wavelength/diam ratio", + diam=diam, lam_over_diam=lam_over_diam) if diam is not None: self.lam_over_diam = (1.e-9*lam/diam)*radians/self.scale_unit else: diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index e13be1772e7..df818214534 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -116,7 +116,7 @@ def __add__(self, other): from . import wcs if not wcs.compatible(self.wcs, other.wcs): import warnings - warnings.warn("Adding two CorrelatedNoise objects with incompatible WCS functions.\n"+ + warnings.warn("Adding two CorrelatedNoise objects with incompatible WCS. " "The result will have the WCS of the first object.", GalSimWarning) return _BaseCorrelatedNoise(self.rng, self._profile + other._profile, self.wcs) @@ -124,7 +124,7 @@ def __sub__(self, other): from . import wcs if not wcs.compatible(self.wcs, other.wcs): import warnings - warnings.warn("Subtracting two CorrelatedNoise objects with incompatible WCS functions.\n"+ + warnings.warn("Subtracting two CorrelatedNoise objects with incompatible WCS. " "The result will have the WCS of the first object.", GalSimWarning) return _BaseCorrelatedNoise(self.rng, self._profile - other._profile, self.wcs) @@ -1397,7 +1397,7 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i if file_name is None: file_name = os.path.join(meta_data.share_dir,'acs_I_unrot_sci_20_cf.fits') if not os.path.isfile(file_name): - raise IOError("The file '"+str(file_name)+"' does not exist.") + raise IOError("The file %r does not exist."%(file_name)) try: cfimage = fits.read(file_name) except KeyboardInterrupt: @@ -1406,8 +1406,8 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i # Give a vaguely helpful warning, then raise the original exception for extra diagnostics import warnings warnings.warn( - "Function getCOSMOSNoise() unable to read FITS image from "+str(file_name)+", "+ - "more information on the error in the following Exception...", GalSimWarning) + "Function getCOSMOSNoise() unable to read FITS image from %r."(file_name), + GalSimWarning) raise # Then check for negative variance before doing anything time consuming diff --git a/galsim/dcr.py b/galsim/dcr.py index ad737e9a261..6c1d04033b0 100644 --- a/galsim/dcr.py +++ b/galsim/dcr.py @@ -22,6 +22,7 @@ apparent zenith angles of an object), as a function of zenith angle, wavelength, temperature, pressure, and water vapor content. """ +from .errors import GalSimIncompatibleValuesError def air_refractive_index_minus_one(wave, pressure=69.328, temperature=293.15, H2O_pressure=1.067): """Return the refractive index of air as function of wavelength. @@ -94,7 +95,9 @@ def zenith_parallactic_angles(obj_coord, zenith_coord=None, HA=None, latitude=No from .angle import degrees if zenith_coord is None: if HA is None or latitude is None: - raise TypeError("Must provide either zenith_coord or (HA, latitude).") + raise GalSimIncompatibleValuesError( + "Must provide either zenith_coord or (HA, latitude).", + HA=HA, latitude=latitude, zenith_coord=zenit_coord) zenith_coord = CelestialCoord(HA + obj_coord.ra, latitude) zenith_angle = obj_coord.distanceTo(zenith_coord) NCP = CelestialCoord(0.0*degrees, 90*degrees) @@ -124,9 +127,10 @@ def parse_dcr_angles(**kwargs): if 'zenith_angle' in kwargs: zenith_angle = kwargs.pop('zenith_angle') parallactic_angle = kwargs.pop('parallactic_angle', 0.0*degrees) - if not isinstance(zenith_angle, Angle) or \ - not isinstance(parallactic_angle, Angle): - raise TypeError("zenith_angle and parallactic_angle must be galsim.Angles.") + if not isinstance(zenith_angle, Angle): + raise TypeError("zenith_angle must be a galsim.Angle.") + if not isinstance(parallactic_angle, Angle): + raise TypeError("parallactic_angle must be a galsim.Angles.") elif 'obj_coord' in kwargs: obj_coord = kwargs.pop('obj_coord') if 'zenith_coord' in kwargs: @@ -135,8 +139,9 @@ def parse_dcr_angles(**kwargs): obj_coord=obj_coord, zenith_coord=zenith_coord) else: if 'HA' not in kwargs or 'latitude' not in kwargs: - raise TypeError("Either zenith_coord or (HA, latitude) is required " + - "when obj_coord is specified.") + raise GalSimIncompatibleValuesError( + "Must provide either zenith_coord or (HA, latitude).", + HA=None, latitude=None, obj_coord=obj_coode) HA = kwargs.pop('HA') latitude = kwargs.pop('latitude') zenith_angle, parallactic_angle = zenith_parallactic_angles( diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 9c780e9ed1e..0ab65613d50 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -231,7 +231,7 @@ def download(url, target, unpack_dir, args, logger): if obsolete: if args.quiet or args.force: - logger.warning("The version currently on disk is obsolete. "+ + logger.warning("The version currently on disk is obsolete. " "Downloading new version.") else: q = "The version currently on disk is obsolete. Download new version?" @@ -239,10 +239,10 @@ def download(url, target, unpack_dir, args, logger): if yn == 'no': do_download = False elif args.force: - logger.info("Target file has already been downloaded and unpacked. "+ + logger.info("Target file has already been downloaded and unpacked. " "Forced re-download.") elif args.quiet: - logger.info("Target file has already been downloaded and unpacked. "+ + logger.info("Target file has already been downloaded and unpacked. " "Not re-downloading.") do_download = False args.save = True # Don't delete it! diff --git a/galsim/errors.py b/galsim/errors.py index 1be975d2ac5..7a6bcba69a3 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -86,7 +86,7 @@ class GalSimUndefinedBoundsError(GalSimError): pass -class GalSimIncompatibleValuesError(GalSimError, ValueError): +class GalSimIncompatibleValuesError(GalSimError, ValueError, TypeError): """A GalSim-specific exception class indicating that 2 or more user-input values are incompatible as given. @@ -100,7 +100,7 @@ def __init__(self, message, **kwargs): super().__init__(message) -class GalSimSEDError(GalSimError): +class GalSimSEDError(GalSimError, TypeError): """A GalSim-specific exception class indicating an attempt to do something invalid for the kind of SED that is present. Typically involving a dimensionless SED where a spectral SED is required (or vice versa). diff --git a/galsim/exponential.py b/galsim/exponential.py index dc143e74118..9091fe2e1c1 100644 --- a/galsim/exponential.py +++ b/galsim/exponential.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimIncompatibleValuesError class Exponential(GSObject): @@ -75,15 +76,15 @@ class Exponential(GSObject): def __init__(self, half_light_radius=None, scale_radius=None, flux=1., gsparams=None): if half_light_radius is not None: if scale_radius is not None: - raise TypeError( - "Only one of scale_radius and half_light_radius may be " + - "specified for Exponential") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) else: scale_radius = half_light_radius / Exponential._hlr_factor elif scale_radius is None: - raise TypeError( - "Either scale_radius or half_light_radius must be " + - "specified for Exponential") + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) self._r0 = float(scale_radius) self._flux = float(flux) self._gsparams = GSParams.check(gsparams) diff --git a/galsim/fits.py b/galsim/fits.py index 572ea371278..43eaf7add6c 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -28,7 +28,7 @@ import numpy as np from .image import Image -from .errors import GalSimError, GalSimValueError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimWarning, GalSimIncompatibleValuesError ############################################################################################## @@ -475,9 +475,8 @@ def _check_hdu(hdu, pyfits_compress): if pyfits_compress: if not isinstance(hdu, pyfits.CompImageHDU): # pragma: no cover if isinstance(hdu, pyfits.BinTableHDU): - raise IOError('Expecting a CompImageHDU, but got a BinTableHDU\n' + - 'Probably your pyfits installation does not have the pyfitsComp module '+ - 'installed.') + raise IOError('Expecting a CompImageHDU, but got a BinTableHDU. Probably your ' + 'pyfits installation does not have the pyfitsComp module installed.') elif isinstance(hdu, pyfits.ImageHDU): import warnings warnings.warn("Expecting a CompImageHDU, but found an uncompressed ImageHDU", @@ -585,9 +584,11 @@ def write(image, file_name=None, dir=None, hdu_list=None, clobber=True, compress file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if hdu_list is None: hdu_list = pyfits.HDUList() @@ -634,9 +635,11 @@ def writeMulti(image_list, file_name=None, dir=None, hdu_list=None, clobber=True file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if hdu_list is None: hdu_list = pyfits.HDUList() @@ -711,9 +714,11 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if hdu_list is None: hdu_list = pyfits.HDUList() @@ -729,7 +734,7 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, else: nimages = len(image_list) if (nimages == 0): - raise IndexError("In writeCube: image_list has no images") + raise GalSimValueError("In writeCube: image_list has no images", image_list) im = image_list[0] dtype = im.array.dtype nx = im.xmax - im.xmin + 1 @@ -745,8 +750,9 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, nx_k = im.xmax-im.xmin+1 ny_k = im.ymax-im.ymin+1 if nx_k != nx or ny_k != ny: - raise IndexError("In writeCube: image %d has the wrong shape"%k + - "Shape is (%d,%d). Should be (%d,%d)"%(nx_k,ny_k,nx,ny)) + raise GalSimValueError("In writeCube: image %d has the wrong shape. " + "Shape is (%d,%d) should be (%d,%d)"%(k,nx_k,ny_k,nx,ny), + im) cube[k,:,:] = image_list[k].array @@ -855,9 +861,11 @@ def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto'): file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list to read()") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list to read()") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if file_name: hdu_list, fin = _read_file(file_name, dir, file_compress) @@ -871,8 +879,8 @@ def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto'): data = hdu.data else: import warnings - warnings.warn("No C++ Image template instantiation for data type %s" % dt + - " Using numpy.float64 instead.", GalSimWarning) + warnings.warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt), GalSimWarning) data = hdu.data.astype(np.float64) image = Image(array=data) @@ -930,9 +938,11 @@ def readMulti(file_name=None, dir=None, hdu_list=None, compression='auto'): file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list to readMulti()") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list to readMulti()") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if file_name: hdu_list, fin = _read_file(file_name, dir, file_compress) @@ -1003,9 +1013,11 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut file_compress, pyfits_compress = _parse_compression(compression,file_name) if file_name and hdu_list is not None: - raise TypeError("Cannot provide both file_name and hdu_list to read()") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) if not (file_name or hdu_list is not None): - raise TypeError("Must provide either file_name or hdu_list to read()") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) if file_name: hdu_list, fin = _read_file(file_name, dir, file_compress) @@ -1019,8 +1031,8 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut data = hdu.data else: import warnings - warnings.warn("No C++ Image template instantiation for data type %s" % dt + - " Using numpy.float64 instead.", GalSimWarning) + warnings.warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt), GalSimWarning) data = hdu.data.astype(np.float64) nimages = data.shape[0] @@ -1186,11 +1198,14 @@ def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=Non from ._pyfits import pyfits if header and file_name: - raise TypeError("Cannot provide both file_name and header to FitsHeader") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and header", file_name=file_name, header=header) if header and hdu_list: - raise TypeError("Cannot provide both hdu_list and header to FitsHeader") + raise GalSimIncompatibleValuesError( + "Cannot provide both hdu_list and header", hdu_list=hdu_list, header=header) if file_name and hdu_list: - raise TypeError("Cannot provide both file_name and hdu_list to FitsHeader") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) # Interpret a string header as though it were passed as file_name. if isinstance(header, basestring): @@ -1275,7 +1290,7 @@ def __delitem__(self, key): if key in self.header: del self.header[key] else: - raise KeyError("key "+key+" not in FitsHeader") + raise KeyError("key %r not in FitsHeader"%(key)) def __getitem__(self, key): return self.header[key] diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 1471b8989c8..8bf7a6c4b4e 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -29,7 +29,7 @@ from .angle import radians, arcsec, degrees, AngleUnit from . import _galsim from . import fits -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimWarning ######################################################################################### # @@ -125,9 +125,12 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if compression is not 'auto': self._tag += ', compression=%r'%compression if header is not None: - raise TypeError("Cannot provide both file_name and pyfits header") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) if wcs is not None: - raise TypeError("Cannot provide both file_name and wcs") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and wcs", file_name=file_name, wcs=wcs) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) header = hdu.header @@ -146,11 +149,14 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if self._tag is None: self.header = header if wcs is not None: - raise TypeError("Cannot provide both pyfits header and wcs") + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcs", header=header, wcs=wcs) wcs = self._load_from_header(header, hdu) if wcs is None: - raise TypeError("Must provide one of file_name, header, or wcs") + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcs", + file_name=file_name, header=header, wcs=wcs) if file_name is not None: fits.closeHDUList(hdu_list, fin) @@ -434,9 +440,13 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if compression is not 'auto': self._tag += ', compression=%r'%compression if header is not None: - raise TypeError("Cannot provide both file_name and pyfits header") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) if wcsinfo is not None: - raise TypeError("Cannot provide both file_name and wcsinfo") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and wcsinfo", + file_name=file_name, wcsinfo=wcsinfo) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) header = hdu.header @@ -445,11 +455,14 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if self._tag is None: self.header = header if wcsinfo is not None: - raise TypeError("Cannot provide both pyfits header and wcsinfo") + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcsinfo", header=header, wcsinfo=wcsinfo) wcsinfo = self._load_from_header(header, hdu) if wcsinfo is None: - raise TypeError("Must provide one of file_name, header, or wcsinfo") + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcsinfo", + file_name=file_name, header=header, wcsinfo=wcsinfo) # We can only handle WCS with 2 pixel axes (given by Nin) and 2 WCS axes # (given by Nout). @@ -786,8 +799,8 @@ def _xy(self, ra, dec, color=None): if len(vals) < 6: raise GalSimError('wcstools sky2xy returned invalid result for %f,%f'%(ra,dec)) if len(vals) > 6: - warnings.warn('wcstools sky2xy indicates that %f,%f is off the image\n'%(ra,dec) + - 'output is %r'%results, GalSimWarning) + warnings.warn('wcstools sky2xy indicates that %f,%f is off the image. ' + 'output is %r'%(ra,dec,results), GalSimWarning) x = float(vals[4]) y = float(vals[5]) @@ -932,12 +945,15 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if compression is not 'auto': self._tag += ', compression=%r'%compression if header is not None: - raise TypeError("Cannot provide both file_name and pyfits header") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) header = hdu.header if header is None: - raise TypeError("Must provide either file_name or header") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or header", file_name=file_name, header=header) # Read the wcs information from the header. self._read_header(header) @@ -971,7 +987,7 @@ def _read_header(self, header): if ctype1[5:] != ctype2[5:]: raise GalSimError("ctype1, ctype2 do not seem to agree on the WCS type") self.wcs_type = ctype1[5:] - if self.wcs_type in [ 'TAN', 'TPV', 'TNX', 'TAN-SIP' ]: + if self.wcs_type in ('TAN', 'TPV', 'TNX', 'TAN-SIP'): self.projection = 'gnomonic' elif self.wcs_type == 'STG': self.projection = 'stereographic' @@ -980,7 +996,9 @@ def _read_header(self, header): elif self.wcs_type == 'ARC': self.projection = 'postel' else: - raise GalSimError("GSFitsWCS cannot read files using WCS type "+self.wcs_type) + raise GalSimValueError("GSFitsWCS cannot read files using given wcs_type.", + self.wcs_type, + ('TAN', 'TPV', 'TNX', 'TAN-SIP', 'STG', 'ZEA', 'ARC')) crval1 = float(header['CRVAL1']) crval2 = float(header['CRVAL2']) crpix1 = float(header['CRPIX1']) @@ -1074,9 +1092,9 @@ def _read_tpv(self, header): 'PV1_11' in header and header['PV1_11'] != 0.0 or 'PV2_3' in header and header['PV1_3'] != 0.0 or 'PV2_11' in header and header['PV1_11'] != 0.0 ): - raise NotImplementedError("We don't implement odd powers of r for TPV") + raise NotImplementedError("TPV not implemented for odd powers of r") if 'PV1_12' in header: - raise NotImplementedError("We don't implement past 3rd order terms for TPV") + raise NotImplementedError("TPV not implemented past 3rd order terms") # Another strange thing is that the two matrices are defined in the opposite order # with respect to their element ordering. And remember that we skipped k=3 in the @@ -1377,7 +1395,9 @@ def _xy(self, ra, dec, color=None): def _local(self, image_pos, world_pos, color=None): if image_pos is None: if world_pos is None: - raise TypeError("Either image_pos or world_pos must be provided") + raise GalSimIncompatibleValuesError( + "Either image_pos or world_pos must be provided", + image_pos=image_pos, world_pos=world_pos) image_pos = self._posToImage(world_pos, color=color) # The key lemma here is that chain rule for jacobians is just matrix multiplication. @@ -1674,13 +1694,16 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', """ if file_name is not None: if header is not None: - raise TypeError("Cannot provide both file_name and pyfits header") + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) header = fits.FitsHeader(file_name=file_name, dir=dir, hdu=hdu, compression=compression, text_file=text_file) else: file_name = 'header' # For sensible error messages below. if header is None: - raise TypeError("Must provide either file_name or header") + raise GalSimIncompatibleValuesError( + "Must provide either file_name or header", file_name=file_name, header=header) for wcs_type in fits_wcs_types: try: @@ -1703,8 +1726,8 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', # Finally, this one is really the last resort, since it only reads in the linear part of the # WCS. It defaults to the equivalent of a pixel scale of 1.0 if even these are not present. if not suppress_warning: - warnings.warn("All the fits WCS types failed to read "+file_name+". " + - "Using AffineTransform instead, which will not really be correct.", + warnings.warn("All the fits WCS types failed to read %r. Using AffineTransform " + "instead, which will not really be correct."%(file_name), GalSimWarning) wcs = AffineTransform._readHeader(header) return wcs diff --git a/galsim/gaussian.py b/galsim/gaussian.py index 45281f20af4..3335b702b84 100644 --- a/galsim/gaussian.py +++ b/galsim/gaussian.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimIncompatibleValuesError class Gaussian(GSObject): @@ -84,22 +85,22 @@ class Gaussian(GSObject): def __init__(self, half_light_radius=None, sigma=None, fwhm=None, flux=1., gsparams=None): if fwhm is not None : if sigma is not None or half_light_radius is not None: - raise TypeError( - "Only one of sigma, fwhm, and half_light_radius may be " + - "specified for Gaussian") + raise GalSimIncompatibleValuesError( + "Only one of sigma, fwhm, and half_light_radius may be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) else: sigma = fwhm / Gaussian._fwhm_factor elif half_light_radius is not None: if sigma is not None: - raise TypeError( - "Only one of sigma, fwhm, and half_light_radius may be " + - "specified for Gaussian") + raise GalSimIncompatibleValuesError( + "Only one of sigma, fwhm, and half_light_radius may be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) else: sigma = half_light_radius / Gaussian._hlr_factor elif sigma is None: - raise TypeError( - "One of sigma, fwhm, or half_light_radius must be " + - "specified for Gaussian") + raise GalSimIncompatibleValuesError( + "One of sigma, fwhm, and half_light_radius must be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) self._sigma = float(sigma) self._flux = float(flux) diff --git a/galsim/gsobject.py b/galsim/gsobject.py index cad2a84b940..c906f34c94c 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -1795,8 +1795,8 @@ def drawFFT_makeKImage(self, image): if Nk > self.gsparams.maximum_fft_size: raise GalSimError( - "drawFFT requires an FFT that is too large: %s. "%Nk + - "If you can handle the large FFT, you may update gsparams.maximum_fft_size.") + "drawFFT requires an FFT that is too large: %s. If you can handle " + "the large FFT, you may update gsparams.maximum_fft_size."%(Nk)) bounds = _BoundsI(0,Nk//2,-Nk//2,Nk//2) if image.dtype in [ np.complex128, np.float64, np.int32, np.uint32 ]: @@ -1983,13 +1983,12 @@ def _calculate_nphotons(self, n_photons, poisson_flux, max_extra_noise, rng): if iN <= 0: # pragma: no cover import warnings - warnings.warn("Automatic n_photons calculation did not end up with positive N. " + - "(n_photons = %s) No photons will be shot. "%n_photons + - "prof = %s "%self + - "flux = %s "%self.flux + - "poisson_flux = %s "%poisson_flux + - "max_extra_noise = %s "%max_extra_noise + - "g = %s "%g, GalSimWarning) + warnings.warn("Automatic n_photons calculation did not end up with positive N. " + "(n_photons = {0}) No photons will be shot. " + " prof = {1}\n flux = {2}\n poisson_flux = {3}\n" + " max_extra_noise = {4}\n g = {5}".format( + n_photons, self, self.flux, poisson_flux, max_extra_noise, g), + GalSimWarning) return 0, 1. return iN, g @@ -2122,7 +2121,7 @@ def drawPhot(self, image, gain=1., add_to_image=False, # so the traceback shows as much detail as possible. import warnings warnings.warn( - "Unable to draw this GSObject with photon shooting. Perhaps it is a "+ + "Unable to draw this GSObject with photon shooting. Perhaps it is a " "Deconvolve or is a compound including one or more Deconvolve objects.", GalSimWarning) raise diff --git a/galsim/image.py b/galsim/image.py index 4d600037b64..409397cbbb3 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -38,8 +38,7 @@ # the following (closed, marked "wontfix") ticket on the numpy issue tracker: # http://projects.scipy.org/numpy/ticket/1246 -alt_int32 = ( np.array([0]).astype(np.int16) + - np.array([0]).astype(np.int32) ).dtype.type +alt_int32 = (np.array([0], dtype=np.int16) + np.array([0], dtype=np.int32)).dtype.type class Image(object): @@ -324,7 +323,8 @@ def __init__(self, *args, **kwargs): # Construct the image attribute if (ncol is not None or nrow is not None): if ncol is None or nrow is None: - raise TypeError("Both nrow and ncol must be provided") + raise GalSimIncompatibleValuesError( + "Both nrow and ncol must be provided", ncol=ncol, nrow=nrow) if ncol != int(ncol) or nrow != int(nrow): raise TypeError("nrow, ncol must be integers") ncol = int(ncol) @@ -347,12 +347,14 @@ def __init__(self, *args, **kwargs): if make_const or not array.flags.writeable: self._array.flags.writeable = False if init_value is not None: - raise TypeError("Cannot specify init_value with array") + raise GalSimIncompatibleValuesError( + "Cannot specify init_value with array", init_value=init_value, array=array) elif image is not None: if not isinstance(image, Image): raise TypeError("image must be an Image") if init_value is not None: - raise TypeError("Cannot specify init_value with image") + raise GalSimIncompatibleValuesError( + "Cannot specify init_value with image", init_value=init_value, image=image) if wcs is None and scale is None: wcs = image.wcs self._bounds = image.bounds @@ -372,12 +374,15 @@ def __init__(self, *args, **kwargs): self._array = np.zeros(shape=(1,1), dtype=self._dtype) self._bounds = BoundsI() if init_value is not None: - raise TypeError("Cannot specify init_value without setting an initial size") + raise GalSimIncompatibleValuesError( + "Cannot specify init_value without setting an initial size", + init_value, ncol=ncol, nrow=nrow, bounds=bounds) # Construct the wcs attribute if scale is not None: if wcs is not None: - raise TypeError("Cannot provide both scale and wcs to Image constructor") + raise GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs to Image constructor", wcs=wcs, scale=scale) self.wcs = PixelScale(float(scale)) else: if wcs is not None and not isinstance(wcs,BaseWCS): @@ -492,14 +497,14 @@ def scale(self): if self.wcs.isPixelScale(): return self.wcs.scale else: - raise TypeError("image.wcs is not a simple PixelScale; scale is undefined.") + raise GalSimError("image.wcs is not a simple PixelScale; scale is undefined.") else: return None @scale.setter def scale(self, value): if self.wcs is not None and not self.wcs.isPixelScale(): - raise TypeError("image.wcs is not a simple PixelScale; scale is undefined.") + raise GalSimError("image.wcs is not a simple PixelScale; scale is undefined.") else: self.wcs = PixelScale(value) @@ -995,11 +1000,13 @@ def view(self, scale=None, wcs=None, origin=None, center=None, make_const=False) @param make_const Make the view's data array immutable. [default: False] """ if origin is not None and center is not None: - raise TypeError("Cannot provide both center and origin") + raise GalSimIncompatibleValuesError( + "Cannot provide both center and origin", center=center, origin=origin) if scale is not None: if wcs is not None: - raise TypeError("Cannot provide both scale and wcs") + raise GalSimIncompatibleValeusError( + "Cannot provide both scale and wcs", scale=scale, wcs=wcs) wcs = PixelScale(scale) elif wcs is not None: if not isinstance(wcs,BaseWCS): diff --git a/galsim/inclined.py b/galsim/inclined.py index 94c7f42df11..86da92d0c9a 100644 --- a/galsim/inclined.py +++ b/galsim/inclined.py @@ -25,7 +25,7 @@ from .exponential import Exponential from .angle import Angle from .position import PositionD -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class InclinedExponential(GSObject): @@ -103,9 +103,9 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale if not scale_radius > 0.: raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) if half_light_radius is not None: - raise TypeError( - "Only one of scale_radius and half_light_radius may be " + - "specified for InclinedExponential") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) self._r0 = float(scale_radius) elif half_light_radius is not None: if not half_light_radius > 0.: @@ -113,18 +113,18 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale # Use the factor from the Exponential class self._r0 = float(half_light_radius) / Exponential._hlr_factor else: - raise TypeError( - "Either scale_radius or half_light_radius must be " + - "specified for InclinedExponential") + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) # Check that the height specification is valid if scale_height is not None: if not scale_height > 0.: raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) if scale_h_over_r is not None: - raise TypeError( - "Only one of scale_height and scale_h_over_r may be " + - "specified for InclinedExponential") + raise GalSimIncompatibleValuesError( + "Only one of scale_height and scale_h_over_r may be specified.", + scale_height=scale_height, scale_h_over_r=scale_h_over_r) self._h0 = float(scale_height) else: if scale_h_over_r is None: @@ -173,9 +173,9 @@ def __hash__(self): self.scale_height, self.flux, self.gsparams)) def __repr__(self): - return ('galsim.InclinedExponential(inclination=%r, scale_radius=%r, scale_height=%r, ' + - 'flux=%r, gsparams=%r)') % ( - self.inclination, self.scale_radius, self.scale_height, self.flux, self.gsparams) + return ('galsim.InclinedExponential(inclination=%r, scale_radius=%r, scale_height=%r, ' + 'flux=%r, gsparams=%r)')%( + self.inclination, self.scale_radius, self.scale_height, self.flux, self.gsparams) def __str__(self): s = 'galsim.InclinedExponential(inclination=%s, scale_radius=%s, scale_height=%s' % ( @@ -315,9 +315,9 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc if not scale_radius > 0.: raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) if half_light_radius is not None: - raise TypeError( - "Only one of scale_radius and half_light_radius may be " + - "specified for InclinedSersic") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified.", + half_light_radius=half_light_radius, scale_radius=scale_radius) self._r0 = float(scale_radius) self._hlr = 0. elif half_light_radius is not None: @@ -331,18 +331,18 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc raise GalSimRangerror("Sersic trunc must be > sqrt(2) * half_light_radius") self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) else: - raise TypeError( - "Either scale_radius or half_light_radius must be " + - "specified for InclinedSersic") + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) # Parse the height options if scale_height is not None: if not scale_height > 0.: raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) if scale_h_over_r is not None: - raise TypeError( - "Only one of scale_height and scale_h_over_r may be " + - "specified for InclinedExponential") + raise GalSimIncompatibleValuesError( + "Only one of scale_height and scale_h_over_r may be specified", + scale_height=scale_height, scale_h_over_r=scale_h_over_r) self._h0 = float(scale_height) else: if scale_h_over_r is None: @@ -405,10 +405,10 @@ def __hash__(self): return hash(("galsim.InclinedSersic", self.n, self.inclination, self.scale_radius, self.scale_height, self.flux, self.trunc, self.gsparams)) def __repr__(self): - return ('galsim.InclinedSersic(n=%r, inclination=%r, scale_radius=%r, scale_height=%r, ' + - 'flux=%r, trunc=%r, gsparams=%r)') % (self.n, - self.inclination, self.scale_radius, self.scale_height, self.flux, self.trunc, - self.gsparams) + return ('galsim.InclinedSersic(n=%r, inclination=%r, scale_radius=%r, scale_height=%r, ' + 'flux=%r, trunc=%r, gsparams=%r)')%( + self.n, self.inclination, self.scale_radius, self.scale_height, self.flux, + self.trunc, self.gsparams) def __str__(self): s = 'galsim.InclinedSersic(n=%s, inclination=%s, scale_radius=%s, scale_height=%s' % ( diff --git a/galsim/integ.py b/galsim/integ.py index 66f357dffc8..f3d1d1214be 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -97,7 +97,7 @@ def trapz(func, min, max, points=10000): if (np.max(points) > max) or (np.min(points) < min): raise GalSimRangeError("Points outside of specified range", points, min, max) elif int(points) != points: - raise TypeError("'npoints' must be integer type or array") + raise TypeError("npoints must be integer type or array") else: points = np.linspace(min, max, points) diff --git a/galsim/interpolant.py b/galsim/interpolant.py index 9f696474843..8d24f914634 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -36,7 +36,7 @@ class Interpolant(object): """ def __init__(self): raise NotImplementedError( - "The Interpolant bas class should not be instantiated directly. "+ + "The Interpolant bas class should not be instantiated directly. " "Use one of the subclasses instead, or use the `from_name` factory function.") @staticmethod diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 5d2ffe8e9b0..14694c591a3 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -317,7 +317,8 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # Set the wcs if necessary if scale is not None: if wcs is not None: - raise TypeError("Cannot provide both scale and wcs to InterpolatedImage") + raise GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs to InterpolatedImage", scale=scale, wcs=wcs) self._image.wcs = PixelScale(scale) elif wcs is not None: if not isinstance(wcs, BaseWCS): @@ -488,7 +489,7 @@ def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): InterpolatedImage._cache_noise_pad[noise_pad] = noise else: raise GalSimValueError( - "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename "+ + "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename " "containing an image to use to make a CorrelatedNoise.", noise_pad) else: diff --git a/galsim/kolmogorov.py b/galsim/kolmogorov.py index 5afa8ba99f9..edf8d8ea370 100644 --- a/galsim/kolmogorov.py +++ b/galsim/kolmogorov.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import GalSimIncompatibleValuesError class Kolmogorov(GSObject): @@ -155,25 +156,33 @@ def __init__(self, lam_over_r0=None, fwhm=None, half_light_radius=None, lam=None if fwhm is not None : if any(item is not None for item in (lam_over_r0, lam, r0, r0_500, half_light_radius)): - raise TypeError( - "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or "+ - "r0_500) may be specified for Kolmogorov") + raise GalSimIncompatibleValuesError( + "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "may be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) self._lor0 = float(fwhm) / Kolmogorov._fwhm_factor elif half_light_radius is not None: if any(item is not None for item in (lam_over_r0, lam, r0, r0_500)): - raise TypeError( - "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or "+ - "r0_500) may be specified for Kolmogorov") + raise GalSimIncompatibleValuesError( + "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "may be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) self._lor0 = float(half_light_radius) / Kolmogorov._hlr_factor elif lam_over_r0 is not None: if any(item is not None for item in (lam, r0, r0_500)): - raise TypeError("Cannot specify lam, r0 or r0_500 in conjunction with lam_over_r0.") + raise GalSimIncompatibleValuesError( + "Cannot specify lam, r0 or r0_500 in conjunction with lam_over_r0.", + lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500) self._lor0 = float(lam_over_r0) else: if lam is None or (r0 is None and r0_500 is None): - raise TypeError( - "One of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or "+ - "r0_500) must be specified for Kolmogorov") + raise GalSimIncompatibleValuesError( + "One of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "must be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) # In this case we're going to use scale_unit, so parse it in case of string input: if scale_unit is None: scale_unit = arcsec diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index acb7291b8ad..847e2b02ef0 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -591,9 +591,9 @@ def _convert_power_function(self, pf, pf_str): pf(1.0) except Exception as e: raise GalSimValueError( - "String %s must either be a valid filename or something that "%pf_str+ - "can eval to a function of k.\n"+ - "Caught error: {0}".format(e), origpf) + "String {0} must either be a valid filename or something that " + "can eval to a function of k.\n" + "Caught error: {1}".format(pf_str, e), origpf) # Check that the function is sane. # Note: Only try tests below if it's not a LookupTable. @@ -974,9 +974,9 @@ def _getSingleShear(self, x, y, ii_g1, ii_g2, periodic): # shear to zero for positions that are outside the original grid. import warnings warnings.warn( - "Warning: position (%f,%f) not within the bounds "%(x,y) + - "of the gridded shear values: " + str(self.bounds) + - ". Returning a shear of (0,0) for this point.", GalSimWarning) + "Warning: position (%f,%f) not within the bounds (%s) of the gridded shear " + "values. Returning a shear of (0,0) for this point."%(x,y,self.bounds), + GalSimWarning) return 0., 0. else: # Treat this as a periodic box. @@ -1072,9 +1072,9 @@ def _getSingleConvergence(self, x, y, ii_kappa, periodic): if not periodic: import warnings warnings.warn( - "Warning: position (%f,%f) not within the bounds "%(x,y) + - "of the gridded convergence values: " + str(self.bounds) + - ". Returning a convergence of 0 for this point.", GalSimWarning) + "Warning: position (%f,%f) not within the bounds (%s) of the gridded " + "convergence values. Returning a convergence of 0 for this point."%( + x,y,self.bounds), GalSimWarning) return 0. else: # Treat this as a periodic box. @@ -1171,9 +1171,9 @@ def _getSingleMagnification(self, x, y, ii_mu, periodic): if not periodic: import warnings warnings.warn( - "Warning: position (%f,%f) not within the bounds "%(x,y) + - "of the gridded convergence values: " + str(self.bounds) + - ". Returning a magnification of 1 for this point.", GalSimWarning) + "Warning: position (%f,%f) not within the bounds (%s) of the gridded " + "convergence values. Returning a magnification of 1 for this point."%( + x,y,self.bounds), GalSimWarning) return 1. else: # Treat this as a periodic box. @@ -1279,9 +1279,9 @@ def _getSingleLensing(self, x, y, ii_g1, ii_g2, ii_mu, periodic): if not periodic: import warnings warnings.warn( - "Warning: position (%f,%f) not within the bounds "%(x,y) + - "of the gridded values: " + str(self.bounds) + - ". Returning 0 for lensing observables at this point.", GalSimWarning) + "Warning: position (%f,%f) not within the bounds (%s) of the gridded " + "values. Returning 0 for lensing observables at this point."%(x,y,self.bounds), + GalSimWarning) return 0., 0., 1. else: # Treat this as a periodic box. diff --git a/galsim/main.py b/galsim/main.py index 8a1d2fdc363..ef814bb8a8a 100644 --- a/galsim/main.py +++ b/galsim/main.py @@ -48,8 +48,8 @@ def parse_args(): parser.add_argument('config_file', type=str, nargs='?', help='the configuration file') parser.add_argument( 'variables', type=str, nargs='*', - help='additional variables or modifications to variables in the config file. ' + - 'e.g. galsim foo.yaml output.nproc=-1 gal.rotate="{type : Random}"') + help='additional variables or modifications to variables in the config file. ' + 'e.g. galsim foo.yaml output.nproc=-1 gal.rotate="{type : Random}"') parser.add_argument( '-v', '--verbosity', type=int, action='store', default=1, choices=(0, 1, 2, 3), help='integer verbosity level: min=0, max=3 [default=1]') @@ -59,8 +59,8 @@ def parse_args(): parser.add_argument( '-f', '--file_type', type=str, action='store', choices=('yaml','json'), default=None, - help=('type of config_file: yaml or json are currently supported. ' + - '[default is to automatically determine the type from the extension]')) + help='type of config_file: yaml or json are currently supported. ' + '[default is to automatically determine the type from the extension]') parser.add_argument( '-m', '--module', type=str, action='append', default=None, help='python module to import before parsing config file') @@ -73,12 +73,12 @@ def parse_args(): 'Used in conjunction with -j (--job)') parser.add_argument( '-j', '--job', type=int, action='store', default=1, - help='set the job number for this particular run. Must be in [1,njobs]. ' + - 'Used in conjunction with -n (--njobs)') + help='set the job number for this particular run. Must be in [1,njobs]. ' + 'Used in conjunction with -n (--njobs)') parser.add_argument( '-x', '--except_abort', action='store_const', default=False, const=True, - help='abort the whole job whenever any file raises an exception rather than ' + - 'continuing on') + help='abort the whole job whenever any file raises an exception rather than ' + 'continuing on') parser.add_argument( '--version', action='store_const', default=False, const=True, help='show the version of GalSim') @@ -112,7 +112,7 @@ def parse_args(): parser.add_option( '-f', '--file_type', type="choice", action='store', choices=('yaml','json'), default=None, - help=('type of config_file: yaml or json are currently supported. ' + + help=('type of config_file: yaml or json are currently supported. ' '[default is to automatically determine the type from the extension]')) parser.add_option( '-m', '--module', type=str, action='append', default=None, @@ -126,12 +126,12 @@ def parse_args(): 'Used in conjunction with -j (--job)') parser.add_option( '-j', '--job', type=int, action='store', default=1, - help='set the job number for this particular run. Must be in [1,njobs]. ' + - 'Used in conjunction with -n (--njobs)') + help='set the job number for this particular run. Must be in [1,njobs]. ' + 'Used in conjunction with -n (--njobs)') parser.add_option( '-x', '--except_abort', action='store_const', default=False, const=True, - help='abort the whole job whenever any file raises an exception rather than ' + - 'just reporting the exception and continuing on') + help='abort the whole job whenever any file raises an exception rather than ' + 'just reporting the exception and continuing on') parser.add_option( '--version', action='store_const', default=False, const=True, help='show the version of GalSim') diff --git a/galsim/moffat.py b/galsim/moffat.py index 65caff2a9c8..f06bf3e1535 100644 --- a/galsim/moffat.py +++ b/galsim/moffat.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimError, GalSimRangeError +from .errors import GalSimError, GalSimRangeError, GalSimIncompatibleValuesError class Moffat(GSObject): @@ -96,9 +96,9 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t # Parse the radius options if half_light_radius is not None: if scale_radius is not None or fwhm is not None: - raise TypeError( - "Only one of scale_radius, half_light_radius, or fwhm may be " + - "specified for Moffat") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius, half_light_radius, or fwhm may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) self._hlr = float(half_light_radius) if self._trunc > 0. and self._trunc <= math.sqrt(2.) * self._hlr: raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.", @@ -107,9 +107,9 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t self._fwhm = 0. elif fwhm is not None: if scale_radius is not None: - raise TypeError( - "Only one of scale_radius, half_light_radius, or fwhm may be " + - "specified for Moffat") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius, half_light_radius, or fwhm may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) self._fwhm = float(fwhm) self._r0 = self._fwhm / (2. * math.sqrt(2.**(1./self._beta) - 1.)) self._hlr = 0. @@ -118,9 +118,9 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t self._hlr = 0. self._fwhm = 0. else: - raise TypeError( - "One of scale_radius, half_light_radius, or fwhm must be " + - "specified for Moffat") + raise GalSimIncompatibleValuesError( + "One of scale_radius, half_light_radius, or fwhm must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) @lazy_property def _sbp(self): diff --git a/galsim/nfw_halo.py b/galsim/nfw_halo.py index 0704aeb34a4..cfdfaa445ec 100644 --- a/galsim/nfw_halo.py +++ b/galsim/nfw_halo.py @@ -24,7 +24,7 @@ from .angle import arcsec from . import integ from . import utilities -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class Cosmology(object): """Basic cosmology calculations. @@ -136,7 +136,9 @@ def __init__(self, mass, conc, redshift, halo_pos=PositionD(0,0), omega_m=None, omega_lam=None, cosmo=None): if omega_m is not None or omega_lam is not None: if cosmo is not None: - raise TypeError("NFWHalo constructor received both cosmo and omega parameters") + raise GalSimIncompatibleValuesError( + "NFWHalo constructor received both cosmo and omega parameters", + cosmo=cosmo, omega_m=omega_m, omega_lam=omega_lam) if omega_m is None: omega_m = 1.-omega_lam if omega_lam is None: omega_lam = 1.-omega_m cosmo = Cosmology(omega_m=omega_m, omega_lam=omega_lam) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index d613b750697..b1f2892ed43 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -1230,7 +1230,7 @@ def __str__(self): (self._screen_list, self.lam, self.exptime)) def __repr__(self): - outstr = ("galsim.PhaseScreenPSF(%r, lam=%r, exptime=%r, flux=%r, aper=%r, theta=%r, " + + outstr = ("galsim.PhaseScreenPSF(%r, lam=%r, exptime=%r, flux=%r, aper=%r, theta=%r, " "interpolant=%r, scale_unit=%r, gsparams=%r)") return outstr % (self._screen_list, self.lam, self.exptime, self.flux, self.aper, self.theta, self.interpolant, self.scale_unit, self.gsparams) @@ -1296,10 +1296,10 @@ def _finalize(self): if observed_stepk < specified_stepk: import warnings warnings.warn( - "The calculated stepk (%g) for PhaseScreenPSF is smaller "%observed_stepk + - "than what was used to build the wavefront (%g). "%specified_stepk + - "This could lead to aliasing problems. " + - "Increasing pad_factor is recommended.", GalSimWarning) + "The calculated stepk (%g) for PhaseScreenPSF is smaller than what was used " + "to build the wavefront (%g). This could lead to aliasing problems. " + "Increasing pad_factor is recommended."%(observed_stepk, specified_stepk), + GalSimWarning) @property def _sbp(self): @@ -1640,7 +1640,9 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., def # OpticalScreen. if lam_over_diam is not None: if lam is not None or diam is not None: - raise TypeError("If specifying lam_over_diam, then do not specify lam or diam") + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify lam or diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) # For combination of lam_over_diam and pupil_plane_im with a specified scale, it's # tricky to determine the actual diameter of the pupil needed by Aperture. So for now, # we just disallow this combination. Please feel free to raise an issue at @@ -1648,20 +1650,26 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., def if pupil_plane_im is not None: if isinstance(pupil_plane_im, basestring): # Filename, therefore specific scale exists. - raise TypeError("If specifying lam_over_diam, then do not " - "specify pupil_plane_im as a filename.") - elif (isinstance(pupil_plane_im, Image) - and pupil_plane_im.scale is not None): - raise TypeError("If specifying lam_over_diam, then do not specify " - "pupil_plane_im with definite scale attribute.") + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_im as " + "as a filename.", + lam_over_diam=lam_over_diam, pupil_plane_im=pupil_plane_im) + elif isinstance(pupil_plane_im, Image) and pupil_plane_im.scale is not None: + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_im " + "with definite scale attribute.", + lam_over_diam=lam_over_diam, pupil_plane_im=pupil_plane_im) elif pupil_plane_scale is not None: - raise TypeError("If specifying lam_over_diam, then do not specify " - "pupil_plane_scale.") + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_scale. ", + lam_over_diam=lam_over_diam, pupil_plane_scale=pupil_plane_scale) lam = 500. # Arbitrary diam = lam*1.e-9 / lam_over_diam * radians / scale_unit else: if lam is None or diam is None: - raise TypeError("If not specifying lam_over_diam, then specify lam AND diam") + raise GalSimIncompatibleValuesError( + "If not specifying lam_over_diam, then specify lam AND diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) # Make the optical screen. optics_screen = OpticalScreen( diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 08bf505b6ce..08747ebe941 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -147,7 +147,7 @@ def __str__(self): return "galsim.AtmosphericScreen(altitude=%s)" % self.altitude def __repr__(self): - return ("galsim.AtmosphericScreen(%r, %r, altitude=%r, r0_500=%r, L0=%r, " + + return ("galsim.AtmosphericScreen(%r, %r, altitude=%r, r0_500=%r, L0=%r, " "vx=%r, vy=%r, alpha=%r, time_step=%r, rng=%r)") % ( self.screen_size, self.screen_scale, self.altitude, self.r0_500, self.L0, self.vx, self.vy, self.alpha, self.time_step, self._orig_rng) @@ -656,7 +656,11 @@ def __init__(self, diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, else: # Make sure no individual aberrations were passed in, since they will be ignored. if any([tip, tilt, defocus, astig1, astig2, coma1, coma2, trefoil1, trefoil2, spher]): - raise TypeError("Cannot pass in individual aberrations and array!") + raise GalSimIncompatibleValuesError( + "Cannot pass in individual aberrations and array.", + tip=tip, tilt=tilt, defocus=defocus, astig1=astig1, astig2=astig2, + coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, + spher=spher, aberrations=aberrations) # Aberrations were passed in, so check for right number of entries. if len(aberrations) <= 2: raise GalSimValueError("Aberrations keyword must have length > 2", aberrations) diff --git a/galsim/photon_array.py b/galsim/photon_array.py index 4bd6142e53a..3047c69c42b 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -196,7 +196,8 @@ def scaleXY(self, scale): def assignAt(self, istart, rhs): "Assign the contents of another PhotonArray to this one starting at istart." if istart + rhs.size() > self.size(): - raise IndexError("The given rhs does not fit into this array starting at %d"%istart) + raise GalSimValueError( + "The given rhs does not fit into this array starting at %d"%istart, rhs) s = slice(istart, istart + rhs.size()) self.x[s] = rhs.x self.y[s] = rhs.y @@ -543,7 +544,7 @@ def __init__(self, base_wavelength, scale_unit=arcsec, **kwargs): # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction # Check that they're valid for kw in self.kw: - if kw not in ['temperature', 'pressure', 'H2O_pressure']: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): raise TypeError("Got unexpected keyword: {0}".format(kw)) self.base_refraction = dcr.get_refraction(self.base_wavelength, self.zenith_angle, diff --git a/galsim/position.py b/galsim/position.py index 5bed903f3a0..ebfabc46f5b 100644 --- a/galsim/position.py +++ b/galsim/position.py @@ -71,7 +71,7 @@ class Position(object): a PositionI by a float or add a PositionI to a PositionD. """ def __init__(self): - raise NotImplementedError("Cannot instantiate the base class. " + + raise NotImplementedError("Cannot instantiate the base class. " "Use either PositionD or PositionI.") def _parse_args(self, *args, **kwargs): @@ -88,8 +88,8 @@ def _parse_args(self, *args, **kwargs): try: self.x, self.y = args[0] except (TypeError, ValueError): - raise TypeError(("Single argument to %s must be either a Position "+ - "or a tuple.")%self.__class__) + raise TypeError("Single argument to %s must be either a Position " + "or a tuple."%self.__class__) else: raise TypeError("%s takes at most 2 arguments (%d given)"%( self.__class__, len(args))) diff --git a/galsim/random.py b/galsim/random.py index 995700eb21b..01c6c3ef144 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -24,7 +24,7 @@ import weakref from . import _galsim -from .errors import GalSimRangeError, GalSimValueError +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError class BaseDeviate(object): """Base class for all the various random deviates. @@ -685,8 +685,9 @@ def __init__(self, seed=None, function=None, x_min=None, if interpolant is None: interpolant='linear' if x_min or x_max: - raise TypeError('Cannot pass x_min or x_max alongside a ' - 'filename in arguments to DistDeviate') + raise GalSimIncompatibleValuesError( + "Cannot pass x_min or x_max with a filename argument", + function=function, x_min=x_min, x_max=x_max) function = LookupTable.from_file(function, interpolant=interpolant) x_min = function.x_min x_max = function.x_max @@ -701,8 +702,8 @@ def __init__(self, seed=None, function=None, x_min=None, function(0.6) # A value unlikely to be a singular point of a function except Exception as e: raise GalSimValueError( - "String function must either be a valid filename or something that "+ - "can eval to a function of x.\n"+ + "String function must either be a valid filename or something that " + "can eval to a function of x.\n" "Caught error: {0}".format(e), self.__function) else: self.__function = weakref.ref(function) # Save the inputs to be used in repr @@ -710,17 +711,22 @@ def __init__(self, seed=None, function=None, x_min=None, if not (isinstance(function, LookupTable) or hasattr(function, '__call__')): raise TypeError('Keyword function must be a callable function or a string') if interpolant: - raise TypeError('Cannot provide an interpolant with a callable function argument') + raise GalSimIncompatibleValuesError( + "Cannot provide an interpolant with a callable function argument", + interpolant=interpolant, function=function) if isinstance(function, LookupTable): if x_min or x_max: - raise TypeError('Cannot provide x_min or x_max with a LookupTable function '+ - 'argument') + raise GalSimIncompatibleValuesError( + "Cannot provide x_min or x_max with a LookupTable function", + function=function, x_min=x_min, x_max=x_max) x_min = function.x_min x_max = function.x_max else: if x_min is None or x_max is None: - raise TypeError('Must provide x_min and x_max when function argument is a '+ - 'regular python callable function') + raise GalSimIncompatibleValuesError( + "Must provide x_min and x_max when function argument is a regular " + "python callable function", + function=function, x_min=x_min, x_max=x_max) # Compute the probability distribution function, pdf(x) if (npoints is None and isinstance(function, LookupTable) and @@ -799,7 +805,7 @@ def _function(self): return self.__function if isinstance(self.__function, str) else self.__function() def __repr__(self): - return ('galsim.DistDeviate(seed=%r, function=%r, x_min=%r, x_max=%r, interpolant=%r, '+ + return ('galsim.DistDeviate(seed=%r, function=%r, x_min=%r, x_max=%r, interpolant=%r, ' 'npoints=%r)')%(self._seed_repr(), self._function, self._xmin, self._xmax, self._interpolant, self._npoints) def __str__(self): diff --git a/galsim/randwalk.py b/galsim/randwalk.py index ccb4853cc63..729a9d4f6a4 100644 --- a/galsim/randwalk.py +++ b/galsim/randwalk.py @@ -196,8 +196,7 @@ def _verify(self): """ from .random import BaseDeviate if not isinstance(self._rng, BaseDeviate): - raise TypeError("rng must be an instance of galsim.BaseDeviate, " - "got %s" % str(self._rng)) + raise TypeError("rng must be an instance of galsim.BaseDeviate, got %s"%self._rng) if self._npoints <= 0: raise GalSimRangeError("npoints must be > 0", self._npoints, 1) diff --git a/galsim/real.py b/galsim/real.py index 408da542fb1..d8c32cbd475 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -202,11 +202,13 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, if rng is None: rng = BaseDeviate() elif not isinstance(rng, BaseDeviate): - raise TypeError("The rng provided to RealGalaxy constructor is not a BaseDeviate") + raise TypeError("The rng provided to RealGalaxy is not a BaseDeviate") self.rng = rng if flux is not None and flux_rescale is not None: - raise TypeError("Cannot supply a flux and a flux rescaling factor!") + raise GalSimIncompatibleValuesError( + "Cannot supply a flux and a flux rescaling factor.", + flux=flux, flux_rescale=flux_rescale) if isinstance(real_galaxy_catalog, tuple): # Special (undocumented) way to build a RealGalaxy without needing the rgc directly @@ -856,9 +858,9 @@ def _parse_files_dirs(file_name, image_dir, sample): 'COSMOS_'+use_sample+'_training_sample') full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): - raise GalSimError('No RealGalaxy catalog found in %s. '%image_dir + - 'Run the program galsim_download_cosmos -s %s '%use_sample + - 'to download catalog and accompanying image files.') + raise GalSimError('No RealGalaxy catalog found in %s. Run the program ' + 'galsim_download_cosmos -s %s to download catalog and accompanying ' + 'image files.'%(image_dir, use_sample)) elif image_dir is None: full_file_name = file_name image_dir = os.path.dirname(file_name) @@ -1017,8 +1019,7 @@ def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng= if rng is None: rng = BaseDeviate() elif not isinstance(rng, BaseDeviate): - raise TypeError("The rng provided to ChromaticRealGalaxy constructor " - "is not a BaseDeviate") + raise TypeError("The rng provided to ChromaticRealGalaxy is not a BaseDeviate") self.rng = rng # Get the index to use in the catalog diff --git a/galsim/scene.py b/galsim/scene.py index 84115008a18..2c43e0c26be 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -216,9 +216,9 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # do try to make a parametric galaxy. import warnings warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. '+ - 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to re-download the COSMOS catalog.', GalSimWarning) + 'You seem to have an old version of the COSMOS parameter file. ' + 'Please run `galsim_download_cosmos -s %s` to re-download ' + 'the COSMOS catalog.'%(self.use_sample), GalSimWarning) # NB. The pyfits FITS_Rec class has a bug where it makes a copy of the full # record array in each record (e.g. in getParametricRecord) and then doesn't @@ -285,10 +285,10 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, self.selection_cat = None import warnings warnings.warn( - 'File with GalSim selection criteria not found! '+ - 'Not all of the requested exclusions will be performed. '+ - 'Run the program `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to get the necessary selection file.', GalSimWarning) + 'File with GalSim selection criteria not found. ' + 'Not all of the requested exclusions will be performed. ' + 'Run the program `galsim_download_cosmos -s %s` to get the ' + 'necessary selection file.'%(self.use_sample), GalSimWarning) # Finally, impose a cut that the total flux in the postage stamp should be positive, # which excludes a tiny number of galaxies (of order 10 in each sample) with some sky @@ -300,11 +300,11 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, else: import warnings warnings.warn( - 'This version of the COSMOS catalog does not have info about total flux in '+ - 'the galaxy postage stamps. Exclusion of negative-flux stamps in advance '+ - 'cannot be done. '+ - 'Run the program `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to get the updated catalog with this information precomputed.', + 'This version of the COSMOS catalog does not have info about total flux ' + 'in the galaxy postage stamps. Exclusion of negative-flux stamps in ' + 'advance cannot be done. ' + 'Run the program `galsim_download_cosmos -s %s` to get the updated ' + 'catalog with this information precomputed.'%(self.use_sample), GalSimWarning) if exclusion_level in ['bad_fits', 'marginal']: @@ -353,9 +353,9 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # getting the flux. import warnings warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. '+ - 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to re-download the COSMOS catalog to get faster and more accurate selection.', + 'You seem to have an old version of the COSMOS parameter file. ' + 'Please run `galsim_download_cosmos -s %s` to re-download the ' + 'COSMOS catalog to get faster and more accurate selection.'%(self.use_sample), GalSimWarning) sparams = self.param_cat['sersicfit'] @@ -531,10 +531,10 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= if 'hlr' not in self.param_cat.dtype.names: # pragma: no cover import warnings warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. '+ - 'Please run `galsim_download_cosmos -s %s` '%self.use_sample+ - 'to re-download the COSMOS catalog '+ - 'and take advantage of pre-computation of many quantities..', GalSimWarning) + 'You seem to have an old version of the COSMOS parameter file. ' + 'Please run `galsim_download_cosmos -s %s` to re-download the COSMOS ' + 'catalog and take advantage of pre-computation of many quantities..'%( + self.use_sample), GalSimWarning) gal_list = self._makeParametric(indices, chromatic, sersic_prec, gsparams) @@ -551,12 +551,12 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= elif self.use_sample == '25.2': import warnings warnings.warn( - 'Ignoring `deep` argument, because the sample being used already '+ + 'Ignoring `deep` argument, because the sample being used already ' 'corresponds to a flux limit of F814W<25.2', GalSimWarning) else: import warnings warnings.warn( - 'Ignoring `deep` argument, because the sample being used does not '+ + 'Ignoring `deep` argument, because the sample being used does not ' 'corresponds to a flux limit of F814W<23.5', GalSimWarning) # Store the orig_index as gal.index regardless of whether we have a RealGalaxy or not. @@ -906,7 +906,7 @@ def _makeSingleGalaxy(cosmos_catalog, index, gal_type, noise_pad_size=5, deep=Fa else: import warnings warnings.warn( - 'Ignoring `deep` argument, because the sample being used already '+ + 'Ignoring `deep` argument, because the sample being used already ' 'corresponds to a flux limit of F814W<25.2', GalSimWarning) # Store the orig_index as gal.index, since the above RealGalaxy initialization just sets it diff --git a/galsim/sed.py b/galsim/sed.py index 1f400f96206..7c0142293ff 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -180,7 +180,9 @@ def __init__(self, spec, wave_type, flux_type, redshift=0., fast=True, self.flux_type = flux_type self.spectral = self.check_spectral() if not self.spectral and not self.check_dimensionless(): - raise TypeError("Flux_type must be equivalent to a spectral density or dimensionless.") + raise GalSimValueError( + "Flux_type must be equivalent to a spectral density or dimensionless.", + flux_type) try: if self.wave_factor and self.spectral: self.flux_factor = (1*self.flux_type).to(SED._fphotons).value @@ -323,8 +325,8 @@ def _initialize_spec(self): test_value = 0 except Exception as e: raise GalSimValueError( - "String spec must either be a valid filename or something that "+ - "can eval to a function of wave.\n" + + "String spec must either be a valid filename or something that " + "can eval to a function of wave.\n" "Caught error: {0}".format(e), self._orig_spec) from numbers import Real if not isinstance(test_value, Real): @@ -535,7 +537,8 @@ def __mul__(self, other): # Product of two SEDs if isinstance(other, SED): if self.spectral and other.spectral: - raise TypeError("Cannot multiply two spectral densities together.") + raise GalSimIncompatibleValuesError( + "Cannot multiply two spectral densities together.", self_sed=self, other=other) if other._const: return self._mul_scalar(other._spec(42.0)) # const, so can eval anywhere. @@ -576,7 +579,7 @@ def __rmul__(self, other): def __div__(self, other): # Enable division by scalars or dimensionless callables (including dimensionless SEDs.) if isinstance(other, SED) and other.spectral: - raise TypeError("Cannot divide by spectral SED.") + raise GalSimSEDError("Cannot divide by spectral SED.", other) if hasattr(other, '__call__'): spec = lambda w: self(w * (1.0 + self.redshift)) / other(w * (1.0 + self.redshift)) elif isinstance(self._spec, LookupTable): @@ -618,7 +621,8 @@ def __add__(self, other): flux_type = 'fphotons' _spectral = True else: - raise TypeError("Cannot add SEDs with incompatible dimensions.") + raise GalSimIncompatibleValuesError( + "Cannot add SEDs with incompatible dimensions.", self_sed=self, other=other) wave_list, blue_limit, red_limit = combine_wave_list(self, other) @@ -664,7 +668,7 @@ def withFluxDensity(self, target_flux_density, wavelength): @returns the new normalized SED. """ if self.dimensionless: - raise TypeError("Cannot set flux density of dimensionless SED.") + raise GalSimSEDError("Cannot set flux density of dimensionless SED.", self) if isinstance(wavelength, units.Quantity): wavelength_nm = wavelength.to(units.nm, units.spectral()) current_flux_density = self._call(wavelength_nm.value) @@ -738,7 +742,7 @@ def calculateFlux(self, bandpass): """ from . import integ if self.dimensionless: - raise TypeError("Cannot calculate flux of dimensionless SED.") + raise GalSimSEDError("Cannot calculate flux of dimensionless SED.", self) if len(bandpass.wave_list) > 0 or len(self.wave_list) > 0: slop = 1e-6 # nm if (self.blue_limit > bandpass.blue_limit + slop @@ -765,7 +769,7 @@ def calculateMagnitude(self, bandpass): @returns the bandpass magnitude. """ if self.dimensionless: - raise TypeError("Cannot calculate magnitude of dimensionless SED.") + raise GalSimSEDError("Cannot calculate magnitude of dimensionless SED.", self) if bandpass.zeropoint is None: raise GalSimError("Cannot do this calculation for a bandpass without an assigned" " zeropoint") @@ -837,7 +841,7 @@ def calculateDCRMomentShifts(self, bandpass, **kwargs): """ from .dcr import parse_dcr_angles if self.dimensionless: - raise TypeError("Cannot calculate DCR shifts of dimensionless SED.") + raise GalSimSEDError("Cannot calculate DCR shifts of dimensionless SED.", self) zenith_angle, parallactic_angle, kwargs = parse_dcr_angles(**kwargs) @@ -887,7 +891,7 @@ def calculateSeeingMomentRatio(self, bandpass, alpha=-0.2, base_wavelength=500): @returns the ratio of the PSF second moments to the second moments of the reference PSF. """ if self.dimensionless: - raise TypeError("Cannot calculate seeing moment ratio of dimensionless SED.") + raise GalSimSEDError("Cannot calculate seeing moment ratio of dimensionless SED.", self) flux = self.calculateFlux(bandpass) if len(bandpass.wave_list) > 0: x, _, _ = combine_wave_list([self, bandpass]) @@ -968,7 +972,7 @@ def __hash__(self): return self._hash def __repr__(self): - outstr = ('galsim.SED(%r, wave_type=%r, flux_type=%r, redshift=%r, fast=%r,' + + outstr = ('galsim.SED(%r, wave_type=%r, flux_type=%r, redshift=%r, fast=%r,' ' _wave_list=%r, _blue_limit=%r, _red_limit=%s)')%( self._orig_spec, self.wave_type, self._flux_type, self.redshift, self.fast, self.wave_list, self.blue_limit, diff --git a/galsim/sersic.py b/galsim/sersic.py index c0c6ae900ff..60ada9505d1 100644 --- a/galsim/sersic.py +++ b/galsim/sersic.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class Sersic(GSObject): """A class describing a Sersic profile. @@ -219,9 +219,9 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, # Parse the radius options if half_light_radius is not None: if scale_radius is not None: - raise TypeError( - "Only one of scale_radius or half_light_radius may be " + - "specified for Spergel") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius or half_light_radius may be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) self._hlr = float(half_light_radius) if self._trunc == 0. or flux_untruncated: self._flux_fraction = 1. @@ -235,8 +235,9 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, self._r0 = float(scale_radius) self._hlr = 0. else: - raise TypeError( - "Either scale_radius or half_light_radius must be specified for Spergel") + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) if self._trunc > 0.: self._flux_fraction = self.calculateIntegratedFlux(self._trunc) diff --git a/galsim/shapelet.py b/galsim/shapelet.py index c7cc7aa10a6..b52381335ad 100644 --- a/galsim/shapelet.py +++ b/galsim/shapelet.py @@ -282,8 +282,8 @@ def fit(cls, sigma, order, image, center=None, normalization='flux', gsparams=No if image.wcs is not None and not image.wcs.isPixelScale(): # TODO: Add ability for ShapeletFitImage to take jacobian matrix. - raise NotImplementedError("Sorry, cannot (yet) fit a shapelet model to an image "+ - "with a non-trivial WCS.") + raise NotImplementedError("Sorry, cannot (yet) fit a shapelet model to an image " + "with a non-trivial WCS.") # Make it double precision if it is not. image = Image(image, dtype=np.float64, copy=False) diff --git a/galsim/shear.py b/galsim/shear.py index b9b6970649d..c64954d4d41 100644 --- a/galsim/shear.py +++ b/galsim/shear.py @@ -22,7 +22,7 @@ import numpy as np from .angle import Angle, _Angle, radians -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class Shear(object): """A class to represent shears in a variety of ways. @@ -137,12 +137,12 @@ def __init__(self, *args, **kwargs): # g,beta elif 'g' in kwargs: if 'beta' not in kwargs: - raise TypeError( - "Shear constructor requires position angle when g is specified!") + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when g is specified.", + g=kwargs['g'], beta=None) beta = kwargs.pop('beta') if not isinstance(beta, Angle): - raise TypeError( - "The position angle that was supplied is not an Angle instance!") + raise TypeError("beta must be an Angle instance.") g = kwargs.pop('g') if g > 1 or g < 0: raise GalSimRangeError("Requested |shear| is outside [0,1].",g, 0., 1.) @@ -151,12 +151,12 @@ def __init__(self, *args, **kwargs): # e,beta elif 'e' in kwargs: if 'beta' not in kwargs: - raise TypeError( - "Shear constructor requires position angle when e is specified!") + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when e is specified.", + e=kwargs['e'], beta=None) beta = kwargs.pop('beta') if not isinstance(beta, Angle): - raise TypeError( - "The position angle that was supplied is not an Angle instance!") + raise TypeError("beta must be an Angle instance.") e = kwargs.pop('e') if e > 1 or e < 0: raise GalSimRangeError("Requested distortion is outside [0,1].", e, 0., 1.) @@ -165,12 +165,12 @@ def __init__(self, *args, **kwargs): # eta,beta elif 'eta' in kwargs: if 'beta' not in kwargs: - raise TypeError( - "Shear constructor requires position angle when eta is specified!") + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when eta is specified.", + eta=kwargs['eta'], beta=None) beta = kwargs.pop('beta') if not isinstance(beta, Angle): - raise TypeError( - "The position angle that was supplied is not an Angle instance!") + raise TypeError("beta must be an Angle instance.") eta = kwargs.pop('eta') if eta < 0: raise GalSimRangeError("Requested eta is below 0.", eta, 0.) @@ -179,12 +179,12 @@ def __init__(self, *args, **kwargs): # q,beta elif 'q' in kwargs: if 'beta' not in kwargs: - raise TypeError( - "Shear constructor requires position angle when q is specified!") + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when q is specified.", + q=kwargs['q'], beta=None) beta = kwargs.pop('beta') if not isinstance(beta, Angle): - raise TypeError( - "The position angle that was supplied is not an Angle instance!") + raise TypeError("beta must be an Angle instance.") q = kwargs.pop('q') if q <= 0 or q > 1: raise GalSimRangeError("Cannot use requested axis ratio.", q, 0., 1.) @@ -192,7 +192,9 @@ def __init__(self, *args, **kwargs): self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) elif 'beta' in kwargs: - raise TypeError("beta provided to Shear constructor, but not g/e/eta/q") + raise GalSimIncompatibleValuesError( + "beta provided to Shear constructor, but not g/e/eta/q", + beta=kwargs['beta'], e=None, g=None, q=None, eta=None) # check for the case where there are 1 or 2 kwargs that are not valid ones for # initializing a Shear diff --git a/galsim/spergel.py b/galsim/spergel.py index 0b7c87d1575..720f7ac830a 100644 --- a/galsim/spergel.py +++ b/galsim/spergel.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class Spergel(GSObject): @@ -127,17 +127,18 @@ def __init__(self, nu, half_light_radius=None, scale_radius=None, # Parse the radius options if half_light_radius is not None: if scale_radius is not None: - raise TypeError( - "Only one of scale_radius or half_light_radius may be " + - "specified for Spergel") + raise GalSimIncompatibleValuesError( + "Only one of scale_radius or half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) self._hlr = float(half_light_radius) self._r0 = self._hlr / _galsim.SpergelCalculateHLR(self._nu) elif scale_radius is not None: self._r0 = float(scale_radius) self._hlr = 0. else: - raise TypeError( - "Either scale_radius or half_light_radius must be specified for Spergel") + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) @lazy_property def _sbp(self): diff --git a/galsim/table.py b/galsim/table.py index 4383b8d93b8..c19e137225d 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -88,13 +88,10 @@ class LookupTable(object): that all inputs / outputs will still be f, it's just a question of how the interpolation is done. [default: False] """ - def __init__(self, x=None, f=None, interpolant=None, x_log=False, f_log=False): + def __init__(self, x, f, interpolant=None, x_log=False, f_log=False): self.x_log = x_log self.f_log = f_log - if x is None or f is None: - raise TypeError("x and f are required for LookupTable") - # check for proper interpolant if interpolant is None: interpolant = 'spline' diff --git a/galsim/utilities.py b/galsim/utilities.py index c3a6b65ebe2..4061bbd9945 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -151,9 +151,9 @@ def canindex(arg): others.pop(0) elif len(args) == 1: # pragma: no cover if integer: - raise TypeError("Cannot parse argument "+str(args[0])+" as a PositionI") + raise TypeError("Cannot parse argument %s as a PositionI"%(args[0])) else: - raise TypeError("Cannot parse argument "+str(args[0])+" as a PositionD") + raise TypeError("Cannot parse argument %s as a PositionD"%(args[0])) elif len(args) <= 2 + len(others): x = args[0] y = args[1] @@ -703,16 +703,16 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): if len(N)==2: n1,n2 = N else: - raise TypeError("'N' has to be a list or a tuple of two integers") + raise TypeError("N must be a list or a tuple of two integers") if not (n1 == int(n1) and n2 == int(n2)): - raise TypeError("'N' has to be of type int or a list or a tuple of two integers") + raise TypeError("N must be of type int or a list or a tuple of two integers") n1 = int(n1) n2 = int(n2) else: - raise TypeError("'N' has to be of type int or a list or a tuple of two integers") + raise TypeError("N must be of type int or a list or a tuple of two integers") if not isinstance(image, Image): - raise TypeError("'image' has to be an instance of galsim.Image") + raise TypeError("image must be an instance of galsim.Image") y_size,x_size = image.array.shape if x_size%n1 or y_size%n2: @@ -841,16 +841,16 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False if len(N)==2: n1,n2 = N else: - raise TypeError("'N' has to be a list or a tuple of two integers") + raise TypeError("N must be a list or a tuple of two integers") if not (n1 == int(n1) and n2 == int(n2)): - raise TypeError("'N' has to be of type int or a list or a tuple of two integers") + raise TypeError("N must be of type int or a list or a tuple of two integers") n1 = int(n1) n2 = int(n2) else: - raise TypeError("'N' has to be of type int or a list or a tuple of two integers") + raise TypeError("N must be of type int or a list or a tuple of two integers") if len(im_list)<2: - raise TypeError("'im_list' needs to have at least two instances of galsim.Image") + raise TypeError("im_list must have at least two instances of galsim.Image") if (n1*n2 != len(im_list)): raise GalSimIncompatibleValuesError( @@ -862,10 +862,10 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False for offset in offsets: if not isinstance(offset, PositionD): - raise TypeError("'offsets' must be a list of galsim.PositionD instances") + raise TypeError("offsets must be a list of galsim.PositionD instances") if not isinstance(im_list[0], Image): - raise TypeError("'im_list' must be a list of galsim.Image instances") + raise TypeError("im_list must be a list of galsim.Image instances") # These should be the same for all images in `im_list'. y_size, x_size = im_list[0].array.shape @@ -873,7 +873,7 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False for im in im_list[1:]: if not isinstance(im, Image): - raise TypeError("'im_list' must be a list of galsim.Image instances") + raise TypeError("im_list must be a list of galsim.Image instances") if im.array.shape != (y_size,x_size): raise GalSimIncompatibleValuesError( @@ -1144,8 +1144,8 @@ def combine_wave_list(*args): elif isinstance(args[0], (list, tuple)): args = args[0] else: - raise TypeError("Single input argument must be a SED, Bandpass, GSObject, " - " ChromaticObject or a (possibly mixed) list of them.") + raise TypeError("Single input argument must be an SED, Bandpass, GSObject, " + "ChromaticObject or a (possibly mixed) list of them.") blue_limit = 0.0 red_limit = np.inf @@ -1334,7 +1334,7 @@ def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): from .random import BaseDeviate, UniformDeviate # Make sure we got a proper RNG. if not isinstance(rng, BaseDeviate): - raise TypeError("The rng provided to rand_with_replacement() is not a BaseDeviate") + raise TypeError("The rng provided to rand_with_replacement() must be a BaseDeviate") ud = UniformDeviate(rng) # Sanity check the requested number of random indices. diff --git a/galsim/wcs.py b/galsim/wcs.py index 514dcecb9e6..ec9faa37cfc 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -53,7 +53,7 @@ from .position import PositionI, PositionD from .celestial import CelestialCoord from .shear import Shear -from .errors import GalSimError +from .errors import GalSimError, GalSimIncompatibleValuesError class BaseWCS(object): """The base class for all other kinds of WCS transformations. @@ -410,7 +410,9 @@ def local(self, image_pos=None, world_pos=None, color=None): """ if color is None: color = self._color if image_pos and world_pos: - raise TypeError("Only one of image_pos or world_pos may be provided") + raise GalSimIncompatibleValuesError( + "Only one of image_pos or world_pos may be provided", + image_pos=image_pos, world_pos=world_pos) return self._local(image_pos, world_pos, color) def jacobian(self, image_pos=None, world_pos=None, color=None): diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 611a80c540b..5f3305019c6 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -197,9 +197,9 @@ def test_draw_add_commutativity(): +"galsim.chromatic") # As an aside, check for appropriate tests of 'integrator' argument. - assert_raises(TypeError, chromatic_final.drawImage, bandpass, method='no_pixel', + assert_raises(ValueError, chromatic_final.drawImage, bandpass, method='no_pixel', integrator='midp') # minor misspelling - assert_raises(TypeError, chromatic_final.drawKImage, bandpass, + assert_raises(ValueError, chromatic_final.drawKImage, bandpass, integrator='midp') # minor misspelling assert_raises(TypeError, chromatic_final.drawImage, bandpass, method='no_pixel', integrator=galsim.integ.midpt) @@ -533,7 +533,7 @@ def test_chromatic_flux(): int_flux/analytic_flux, 1.0, 3, err_msg="Drawn ChromaticConvolve flux (interpolated) doesn't match analytic prediction") # As an aside, check for appropriate tests of 'integrator' argument. - assert_raises(TypeError, final_int.drawImage, bandpass, integrator='midp') # minor misspelling + assert_raises(ValueError, final_int.drawImage, bandpass, integrator='midp') # minor misspelling assert_raises(TypeError, final_int.drawImage, bandpass, integrator=galsim.integ.midpt) # Go back to no interpolation (this will effect the PSFs that are used below). diff --git a/tests/test_sed.py b/tests/test_sed.py index 3b23d038da7..08d511bb5c5 100644 --- a/tests/test_sed.py +++ b/tests/test_sed.py @@ -435,7 +435,7 @@ def test_SED_init(): sed = galsim.SED(galsim.LookupTable(foo,foo), wave_type=units.Hz, flux_type='flambda') assert_raises(ValueError, sed, 0.5) assert_raises(ValueError, sed, 12.0) - assert_raises(TypeError, galsim.SED, '1', 'nm', units.erg/units.s) + assert_raises(ValueError, galsim.SED, '1', 'nm', units.erg/units.s) assert_raises(ValueError, galsim.SED, '1', 'nm', '2') # Check a few valid calls for when fast=False From b976487218b6b22032b57b1cebbec43d79fb82c7 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 11:47:51 -0400 Subject: [PATCH 13/96] Favor OSError over IOError. (In py3, they are equivalent, but OSError is preferred.) (#755) --- examples/check_diff.py | 2 +- galsim/catalog.py | 2 +- galsim/config/output.py | 6 +++--- galsim/correlatednoise.py | 2 +- galsim/des/des_psfex.py | 22 +++++++++++----------- galsim/download_cosmos.py | 4 ++-- galsim/fits.py | 32 ++++++++++++++++---------------- galsim/fitswcs.py | 12 ++++++------ galsim/real.py | 4 ++-- galsim/scene.py | 4 ++-- galsim/sensor.py | 8 ++++---- share/SConscript | 2 +- tests/test_config_output.py | 8 ++++---- tests/test_scene.py | 2 +- tests/test_sensor.py | 4 ++-- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/examples/check_diff.py b/examples/check_diff.py index b1e13dbad29..4f4cc3f3465 100644 --- a/examples/check_diff.py +++ b/examples/check_diff.py @@ -54,7 +54,7 @@ def report(file_name1, file_name2): try: f1 = pyfits.open(file_name1) f2 = pyfits.open(file_name2) - except IOError as e: + except (IOError, OSError) as e: # Then either at least one of the files doesn't exist, which diff can report for us, # or the files are txt files, which diff can also do. return report_txt(file_name1, file_name2) diff --git a/galsim/catalog.py b/galsim/catalog.py index 2a2c07a0470..a0a15577b64 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -125,7 +125,7 @@ def readAscii(self, comments, _nobjects_only=False): if len(self.data.shape) == 1: self.data = self.data.reshape(1, -1) if len(self.data.shape) != 2: - raise IOError('Unable to parse the input catalog as a 2-d array') + raise OSError('Unable to parse the input catalog as a 2-d array') self.nobjects = self.data.shape[0] self.ncols = self.data.shape[1] diff --git a/galsim/config/output.py b/galsim/config/output.py index 72a0d647646..f67ab553f0b 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -359,12 +359,12 @@ def RetryIO(func, args, ntries, file_name, logger): itry += 1 try: ret = func(*args) - except IOError as e: + except (IOError, OSError) as e: if itry == ntries: # Then this was the last try. Just re-raise the exception. raise else: - logger.warning('File %s: Caught IOError: %s',file_name,str(e)) + logger.warning('File %s: Caught OSError: %s',file_name,str(e)) logger.warning('This is try %d/%d, so sleep for %d sec and try again.', itry,ntries,itry) import time @@ -399,7 +399,7 @@ def EnsureDir(target): raise err elif exists and not os.path.isdir(dir): - raise IOError("tried to make directory '%s' " + raise OSError("tried to make directory '%s' " "but a non-directory file of that " "name already exists" % dir) diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index df818214534..1329c8dc37c 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -1397,7 +1397,7 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i if file_name is None: file_name = os.path.join(meta_data.share_dir,'acs_I_unrot_sci_20_cf.fits') if not os.path.isfile(file_name): - raise IOError("The file %r does not exist."%(file_name)) + raise OSError("The file %r does not exist."%(file_name)) try: cfimage = fits.read(file_name) except KeyboardInterrupt: diff --git a/galsim/des/des_psfex.py b/galsim/des/des_psfex.py index b149cda1580..1a018a42f55 100644 --- a/galsim/des/des_psfex.py +++ b/galsim/des/des_psfex.py @@ -208,27 +208,27 @@ def read(self): # Check for valid values of all these things. if pol_naxis != 2: - raise IOError("PSFEx: Expected POLNAXIS == 2, got %d"%pol_naxis) + raise OSError("PSFEx: Expected POLNAXIS == 2, got %d"%pol_naxis) if not (pol_name1.startswith('X') and pol_name1.endswith('IMAGE')): - raise IOError("PSFEx: Expected POLNAME1 == X*_IMAGE, got %s"%pol_name1) + raise OSError("PSFEx: Expected POLNAME1 == X*_IMAGE, got %s"%pol_name1) if not (pol_name2.startswith('Y') and pol_name2.endswith('IMAGE')): - raise IOError("PSFEx: Expected POLNAME2 == Y*_IMAGE, got %s"%pol_name2) + raise OSError("PSFEx: Expected POLNAME2 == Y*_IMAGE, got %s"%pol_name2) if pol_ngrp != 1: - raise IOError("PSFEx: Current implementation requires POLNGRP == 1, got %d"%pol_ngrp) + raise OSError("PSFEx: Current implementation requires POLNGRP == 1, got %d"%pol_ngrp) if pol_group1 != 1: - raise IOError("PSFEx: Expected POLGRP1 == 1, got %s"%pol_group1) + raise OSError("PSFEx: Expected POLGRP1 == 1, got %s"%pol_group1) if pol_group2 != 1: - raise IOError("PSFEx: Expected POLGRP2 == 1, got %s"%pol_group2) + raise OSError("PSFEx: Expected POLGRP2 == 1, got %s"%pol_group2) if psf_naxis != 3: - raise IOError("PSFEx: Expected PSFNAXIS == 3, got %d"%psf_naxis) + raise OSError("PSFEx: Expected PSFNAXIS == 3, got %d"%psf_naxis) if psf_axis3 != ((pol_deg+1)*(pol_deg+2))//2: - raise IOError("PSFEx: POLDEG and PSFAXIS3 disagree") + raise OSError("PSFEx: POLDEG and PSFAXIS3 disagree") if basis.shape[0] != psf_axis3: - raise IOError("PSFEx: PSFAXIS3 disagrees with actual basis size") + raise OSError("PSFEx: PSFAXIS3 disagrees with actual basis size") if basis.shape[1] != psf_axis2: - raise IOError("PSFEx: PSFAXIS2 disagrees with actual basis size") + raise OSError("PSFEx: PSFAXIS2 disagrees with actual basis size") if basis.shape[2] != psf_axis1: - raise IOError("PSFEx: PSFAXIS1 disagrees with actual basis size") + raise OSError("PSFEx: PSFAXIS1 disagrees with actual basis size") # Save some of these values for use in building the interpolated images self.basis = basis diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 0ab65613d50..7028c230dd3 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -284,9 +284,9 @@ def download(url, target, unpack_dir, args, logger): sys.stdout.flush() next_dot += file_size/100. logger.info("Download complete.") - except IOError as e: + except (IOError, OSError) as e: # Try to give a reasonable suggestion for some common IOErrors. - logger.error("\n\nIOError: %s",str(e)) + logger.error("\n\nOSError: %s",str(e)) if 'Permission denied' in str(e): logger.error("Rerun using sudo %s",script_name) logger.error("If this is not possible, you can download to an alternate location:") diff --git a/galsim/fits.py b/galsim/fits.py index 43eaf7add6c..aad437c4e6d 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -85,7 +85,7 @@ def gunzip_call(self, file): p = subprocess.Popen(["gunzip", "-c", file], stdout=subprocess.PIPE, close_fds=True) fin = BytesIO(p.communicate()[0]) if p.returncode != 0: - raise IOError("Error running gunzip. Return code = %s"%p.returncode) + raise OSError("Error running gunzip. Return code = %s"%p.returncode) p.wait() hdu_list = pyfits.open(fin, 'readonly') return hdu_list, fin @@ -137,7 +137,7 @@ def bunzip2_call(self, file): p = subprocess.Popen(["bunzip2", "-c", file], stdout=subprocess.PIPE, close_fds=True) fin = BytesIO(p.communicate()[0]) if p.returncode != 0: - raise IOError("Error running bunzip2. Return code = %s"%p.returncode) + raise OSError("Error running bunzip2. Return code = %s"%p.returncode) p.wait() hdu_list = pyfits.open(fin, 'readonly') return hdu_list, fin @@ -254,7 +254,7 @@ def gzip_call2(self, hdu_list, file): # pragma: no cover p = subprocess.Popen(["gzip", tmp], close_fds=True) p.communicate() if p.returncode != 0: - raise IOError("Error running gzip. Return code = %s"%p.returncode) + raise OSError("Error running gzip. Return code = %s"%p.returncode) p.wait() os.rename(tmp+".gz",file) else: @@ -262,7 +262,7 @@ def gzip_call2(self, hdu_list, file): # pragma: no cover p = subprocess.Popen(["gzip", "-S", ext, "-f", root], close_fds=True) p.communicate() if p.returncode != 0: - raise IOError("Error running gzip. Return code = %s"%p.returncode) + raise OSError("Error running gzip. Return code = %s"%p.returncode) p.wait() def gzip_call(self, hdu_list, file): @@ -272,7 +272,7 @@ def gzip_call(self, hdu_list, file): hdu_list.writeto(p.stdin) p.communicate() if p.returncode != 0: - raise IOError("Error running gzip. Return code = %s"%p.returncode) + raise OSError("Error running gzip. Return code = %s"%p.returncode) p.wait() def gzip_in_mem(self, hdu_list, file): # pragma: no cover @@ -315,7 +315,7 @@ def bzip2_call2(self, hdu_list, file): # pragma: no cover p = subprocess.Popen(["bzip2", tmp], close_fds=True) p.communicate() if p.returncode != 0: - raise IOError("Error running bzip2. Return code = %s"%p.returncode) + raise OSError("Error running bzip2. Return code = %s"%p.returncode) p.wait() os.rename(tmp+".bz2",file) else: @@ -323,7 +323,7 @@ def bzip2_call2(self, hdu_list, file): # pragma: no cover p = subprocess.Popen(["bzip2", root], close_fds=True) p.communicate() if p.returncode != 0: - raise IOError("Error running bzip2. Return code = %s"%p.returncode) + raise OSError("Error running bzip2. Return code = %s"%p.returncode) p.wait() def bzip2_call(self, hdu_list, file): @@ -333,7 +333,7 @@ def bzip2_call(self, hdu_list, file): hdu_list.writeto(p.stdin) p.communicate() if p.returncode != 0: - raise IOError("Error running bzip2. Return code = %s"%p.returncode) + raise OSError("Error running bzip2. Return code = %s"%p.returncode) p.wait() def bz2_in_mem(self, hdu_list, file): # pragma: no cover @@ -386,7 +386,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) if clobber: os.remove(file) else: - raise IOError('File %r already exists'%file) + raise OSError('File %r already exists'%file) if not file_compress: hdu_list.writeto(file) @@ -475,17 +475,17 @@ def _check_hdu(hdu, pyfits_compress): if pyfits_compress: if not isinstance(hdu, pyfits.CompImageHDU): # pragma: no cover if isinstance(hdu, pyfits.BinTableHDU): - raise IOError('Expecting a CompImageHDU, but got a BinTableHDU. Probably your ' + raise OSError('Expecting a CompImageHDU, but got a BinTableHDU. Probably your ' 'pyfits installation does not have the pyfitsComp module installed.') elif isinstance(hdu, pyfits.ImageHDU): import warnings warnings.warn("Expecting a CompImageHDU, but found an uncompressed ImageHDU", GalSimWarning) else: - raise IOError('Found invalid HDU reading FITS file (expected an ImageHDU)') + raise OSError('Found invalid HDU reading FITS file (expected an ImageHDU)') else: if not isinstance(hdu, pyfits.ImageHDU) and not isinstance(hdu, pyfits.PrimaryHDU): - raise IOError('Found invalid HDU reading FITS file (expected an ImageHDU)') + raise OSError('Found invalid HDU reading FITS file (expected an ImageHDU)') def _get_hdu(hdu_list, hdu, pyfits_compress): @@ -497,12 +497,12 @@ def _get_hdu(hdu_list, hdu, pyfits_compress): if hdu is None: if pyfits_compress: if len(hdu_list) <= 1: - raise IOError('Expecting at least one extension HDU in galsim.read') + raise OSError('Expecting at least one extension HDU in galsim.read') hdu = 1 else: hdu = 0 if len(hdu_list) <= hdu: - raise IOError('Expecting at least %d HDUs in galsim.read'%(hdu+1)) + raise OSError('Expecting at least %d HDUs in galsim.read'%(hdu+1)) hdu = hdu_list[hdu] else: hdu = hdu_list @@ -954,11 +954,11 @@ def readMulti(file_name=None, dir=None, hdu_list=None, compression='auto'): if pyfits_compress: first = 1 if len(hdu_list) <= 1: - raise IOError('Expecting at least one extension HDU in galsim.read') + raise OSError('Expecting at least one extension HDU in galsim.read') else: first = 0 if len(hdu_list) < 1: - raise IOError('Expecting at least one HDU in galsim.readMulti') + raise OSError('Expecting at least one HDU in galsim.readMulti') for hdu in range(first,len(hdu_list)): image_list.append(read(hdu_list=hdu_list, hdu=hdu, compression=pyfits_compress)) diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 8bf7a6c4b4e..78d88150f33 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -677,7 +677,7 @@ def __init__(self, file_name, dir=None, origin=None): if dir: file_name = os.path.join(dir, file_name) if not os.path.isfile(file_name): - raise IOError('Cannot find file '+file_name) + raise OSError('Cannot find file '+file_name) self._file_name = file_name # Check wcstools is installed and that it can read the file. @@ -688,7 +688,7 @@ def __init__(self, file_name, dir=None, origin=None): results = p.communicate()[0] p.stdout.close() if len(results) == 0: - raise IOError('wcstools (specifically xy2sky) was unable to read '+file_name) + raise OSError('wcstools (specifically xy2sky) was unable to read '+file_name) @property def file_name(self): return self._file_name @@ -746,10 +746,10 @@ def _radec(self, x, y, color=None): results = p.communicate()[0] p.stdout.close() if len(results) == 0: - raise IOError('wcstools command xy2sky was unable to read '+ self._file_name) + raise OSError('wcstools command xy2sky was unable to read '+ self._file_name) if results[0] != '*': break if results[0] == '*': - raise IOError('wcstools command xy2sky was unable to read '+self._file_name) + raise OSError('wcstools command xy2sky was unable to read '+self._file_name) lines = results.splitlines() # Each line of output should looke like: @@ -787,10 +787,10 @@ def _xy(self, ra, dec, color=None): results = p.communicate()[0] p.stdout.close() if len(results) == 0: - raise IOError('wcstools (specifically sky2xy) was unable to read '+self._file_name) + raise OSError('wcstools (specifically sky2xy) was unable to read '+self._file_name) if results[0] != '*': break if results[0] == '*': - raise IOError('wcstools (specifically sky2xy) was unable to read '+self._file_name) + raise OSError('wcstools (specifically sky2xy) was unable to read '+self._file_name) # The output should looke like: # ra dec J2000 -> x y diff --git a/galsim/real.py b/galsim/real.py index d8c32cbd475..9e9c0a17cde 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -867,9 +867,9 @@ def _parse_files_dirs(file_name, image_dir, sample): else: full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): - raise IOError(full_file_name+' not found.') + raise OSError(full_file_name+' not found.') if not os.path.isdir(image_dir): - raise IOError(image_dir+' directory does not exist!') + raise OSError(image_dir+' directory does not exist!') return full_file_name, image_dir, use_sample diff --git a/galsim/scene.py b/galsim/scene.py index 2c43e0c26be..0fce7ada9bb 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -245,7 +245,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, try: with pyfits.open(selection_file_name) as fits: self.selection_cat = fits[1].data - except IOError: + except (IOError, OSError): # There's one more option: full_file_name might be the parametric fit file, so # we have to strip off the _fits.fits (instead of just the .fits) selection_file_name = full_file_name[:k-5] + '_selection' + full_file_name[k:] @@ -278,7 +278,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, ((self.selection_cat['min_mask_dist_pixels'] > 11.0) | (self.selection_cat['average_mask_adjacent_pixel_count'] / \ div_val < cut_ratio)) ) - except IOError: + except (IOError, OSError): # We can't make any of the above cuts (or any later ones that depend on the # selection catalog) because we couldn't find the selection catalog. Bummer. Warn # the user, and move on. diff --git a/galsim/sensor.py b/galsim/sensor.py index fa54ec9d2e9..40124a37055 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -168,11 +168,11 @@ def __init__(self, name='lsst_itl_8', strength=1.0, rng=None, diffusion_factor=1 if not os.path.isfile(self.config_file): cfg_file = os.path.join(meta_data.share_dir, 'sensors', self.config_file) if not os.path.isfile(cfg_file): - raise IOError("Cannot locate file %s or %s"%(self.config_file, cfg_file)) + raise OSError("Cannot locate file %s or %s"%(self.config_file, cfg_file)) self.config_file = cfg_file self.vertex_file = os.path.join(meta_data.share_dir, 'sensors', self.vertex_file) if not os.path.isfile(self.vertex_file): - raise IOError("Cannot locate vertex file %s"%(self.vertex_file)) + raise OSError("Cannot locate vertex file %s"%(self.vertex_file)) self.config = self._read_config_file(self.config_file) @@ -205,7 +205,7 @@ def _init_silicon(self): vertex_data = np.loadtxt(self.vertex_file, skiprows = 1) if vertex_data.shape != (Nx * Ny * (4 * NumVertices + 4), 5): - raise IOError("Vertex file %s does not match config file %s"%( + raise OSError("Vertex file %s does not match config file %s"%( self.vertex_file, self.config_file)) self._silicon = _galsim.Silicon(NumVertices, num_elec, Nx, Ny, self.qdist, nrecalc, @@ -316,7 +316,7 @@ def _read_config_file(self, filename): lines = [ l.strip() for l in lines ] lines = [ l.split() for l in lines if len(l) > 0 and l[0] != '#' ] if any([l[1] != '=' for l in lines]): - raise IOError("Error reading config file %s"%filename) + raise OSError("Error reading config file %s"%filename) config = dict([(l[0], l[2]) for l in lines]) # convert strings to int or float values when appropriate for k in config: diff --git a/share/SConscript b/share/SConscript index d1ce939c3b4..39426cfdb11 100644 --- a/share/SConscript +++ b/share/SConscript @@ -26,7 +26,7 @@ else: meta_data_file = os.path.join('..','galsim','meta_data.py') try: f = open(meta_data_file,'w') -except IOError: +except (IOError, OSError): # Probably the user ran sudo scons install without first running plain old scons # (without sudo), so the meta_data.py file is owned by root now. # However, it should still be removable, since the directory should be owned diff --git a/tests/test_config_output.py b/tests/test_config_output.py index 05e6f830af9..26e8aaa21fd 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -770,7 +770,7 @@ def __init__(self, rng): def writeFile(self, *args, **kwargs): p = self.ud() if p < 0.33: - raise IOError("p = %f"%p) + raise OSError("p = %f"%p) else: galsim.fits.writeMulti(*args, **kwargs) @@ -812,10 +812,10 @@ def writeFile(self, file_name, config, base, logger): with CaptureLog() as cl: galsim.config.Process(config, logger=cl.logger) #print(cl.output) - assert "File output/test_flaky_fits_0.fits: Caught IOError" in cl.output + assert "File output/test_flaky_fits_0.fits: Caught OSError" in cl.output assert "This is try 2/6, so sleep for 2 sec and try again." in cl.output assert "file 0: Wrote FlakyFits to file 'output/test_flaky_fits_0.fits'" in cl.output - assert "File output/test_flaky_wt_0.fits: Caught IOError: " in cl.output + assert "File output/test_flaky_wt_0.fits: Caught OSError: " in cl.output assert "This is try 1/6, so sleep for 1 sec and try again." in cl.output assert "file 0: Wrote flaky_weight to 'output/test_flaky_wt_0.fits'" in cl.output @@ -875,7 +875,7 @@ def writeFile(self, file_name, config, base, logger): with CaptureLog() as cl: try: galsim.config.Process(config, logger=cl.logger, except_abort=True) - except IOError as e: + except OSError as e: assert str(e) == "p = 0.126989" #print(cl.output) assert "File output/test_flaky_fits_0.fits not written." in cl.output diff --git a/tests/test_scene.py b/tests/test_scene.py index 8aafbd6adea..ca8681fb976 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -45,7 +45,7 @@ def test_cosmos_basic(): # Check for reasonable exceptions when initializing. # Can't find data (wrong directory). - with assert_raises(IOError): + with assert_raises(OSError): galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits') # Try making galaxies diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 5b7e4fdb08c..6f524cd980d 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -292,8 +292,8 @@ def test_silicon(): do_pickle(s1) do_pickle(s7) - assert_raises(IOError, galsim.SiliconSensor, name='junk') - assert_raises(IOError, galsim.SiliconSensor, name='output') + assert_raises(OSError, galsim.SiliconSensor, name='junk') + assert_raises(OSError, galsim.SiliconSensor, name='output') assert_raises(TypeError, galsim.SiliconSensor, rng=3.4) assert_raises(TypeError, galsim.SiliconSensor, 'lsst_itl_8', rng1) From b5b510ed6b34748265ac4c08c18f050102a6d1f6 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 12:23:41 -0400 Subject: [PATCH 14/96] Add notes to developers giving guidance about which error to raise. (#755) --- galsim/errors.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/galsim/errors.py b/galsim/errors.py index 7a6bcba69a3..ca83fd85bfa 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -21,6 +21,54 @@ from builtins import super +# Note to developers about which exception to throw. +# +# Aside from the below classes, which should be preferred for most errors, we also +# throw the following in some cases. +# +# TypeError: Use this for errors that in a more strongly typed language would probably +# be a compiler error. For instance, it is used for the following errors: +# - a parameter has the wrong type +# - the wrong number of unnamed args when processing `*args` by hand. +# - missing or invalid kwargs when processing `**kwargs` by hand. +# +# OSError: Use this for errors related to I/O, disk access, etc. Note: In Python 2, +# there was a distinction between IOError and OSError, but there was never much +# difference in reality, and in Python 3, they made everything OSError. +# We should just use OSError for all such kinds of errors. +# +# KeyError: Use this for the equivalent of accessing a dict-like object with an invalid key. +# E.g. FitsHeader and Catalog raise this for accessing invalid columns. +# +# IndexError: Use this for the equivalent of accessing a list-like object with an invalid index. +# E.g. RealGalaxyCatalog and Catalog raise this for accessing invalid rows. +# +# NotImplementedError: Use this for features that we have not implemented. Even if there is +# no future intent to do so. E.g. GSObject defines uses this for a number +# of methods that are invalid for non-x-analytic profiles where the +# functionality is not implemented (and never will be) in some of the +# derived classes. +# Also, use it for calls that are invalid in a base class perhaps, but are +# valid for derived classes. E.g. GSObject and Position use this for their +# __init__ implementations. +# +# AttributeError: Use this only for an attempt to access an attribute that an object does not +# have. We don't currently raise this anywhere in GalSim. +# +# RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. +# +# ValueError: Don't use this. Use one of the below exceptions that derive from ValueError. +# +# std::runtime_error: Use this for errors in the C++ layer, and put a try/except guard around +# the C++ call in the Python layer to convert to a GalSimError. E.g. +# GSFitsWCS._invert_pv uses this for non-convergence, but we convert to +# a GalSimError in Python. +# When possible, try to guard against any such events by making appropriate +# checks in the Python layer before dropping down into C++. E.g. Image +# checks for anything that might cause the C++ Image class to throw an +# exception and raises some kind of GalSim exception first. + + class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. """ From 43852f2ad0a14bc092773084401f4ba200f3fe78 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 13:03:34 -0400 Subject: [PATCH 15/96] Convert a few GalSimErrors into more specific exceptions (#755) --- galsim/image.py | 4 ++-- galsim/moffat.py | 6 +++--- galsim/photon_array.py | 3 ++- galsim/scene.py | 2 +- galsim/sed.py | 8 ++++---- galsim/sensor.py | 5 +++-- galsim/vonkarman.py | 2 +- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/galsim/image.py b/galsim/image.py index 409397cbbb3..b119a7e151f 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -1224,7 +1224,7 @@ def getValue(self, x, y): im(pos) or im(x=x,y=y)) """ if not self.bounds.isDefined(): - raise GalSimError("Attempt to access values of an undefined image") + raise GalSimUndefinedBoundsError("Attempt to access values of an undefined image") if not self.bounds.includes(x,y): raise GalSimBoundsError("Attempt to access position not in bounds of image.", PositionI(x,y), self.bounds) @@ -1247,7 +1247,7 @@ def setValue(self, *args, **kwargs): if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) if not self.bounds.isDefined(): - raise GalSimError("Attempt to set value of an undefined image") + raise GalSimUndefinedBoundsError("Attempt to set value of an undefined image") pos, value = utilities.parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) if not self.bounds.includes(pos): diff --git a/galsim/moffat.py b/galsim/moffat.py index f06bf3e1535..ce4e05fbb17 100644 --- a/galsim/moffat.py +++ b/galsim/moffat.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimError, GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError class Moffat(GSObject): @@ -88,8 +88,8 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t self._gsparams = GSParams.check(gsparams) if self._trunc == 0. and self._beta <= 1.1: - raise GalSimError("Moffat profiles with beta <= 1.1 must be truncated") - + raise GalSimRangeError("Moffat profiles with beta <= 1.1 must be truncated", + beta, 1.1) if self._trunc < 0.: raise GalSimRangeError("Moffat trunc must be >= 0", self._trunc, 0.) diff --git a/galsim/photon_array.py b/galsim/photon_array.py index 3047c69c42b..2d844ac6d2a 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -279,7 +279,8 @@ def addTo(self, image): @returns the total flux of photons the landed inside the image bounds. """ if not image.bounds.isDefined(): - raise GalSimError("Attempting to PhotonArray::addTo an Image with undefined Bounds") + raise GalSimUndefinedBoundsError( + "Attempting to PhotonArray::addTo an Image with undefined Bounds") return self._pa.addTo(image._image) @classmethod diff --git a/galsim/scene.py b/galsim/scene.py index 0fce7ada9bb..02431bd69f0 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -521,7 +521,7 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= # call the appropriate helper routine for that case. if gal_type == 'real': if chromatic: - raise GalSimError("Cannot yet make real chromatic galaxies!") + raise NotImplementedError("Cannot yet make real chromatic galaxies!") gal_list = self._makeReal(indices, noise_pad_size, rng, gsparams) else: # If no pre-selection was done based on radius or flux, then we won't have checked diff --git a/galsim/sed.py b/galsim/sed.py index 7c0142293ff..462d48b6862 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -707,8 +707,8 @@ def withMagnitude(self, target_magnitude, bandpass): @returns the new normalized SED. """ if bandpass.zeropoint is None: - raise GalSimError("Cannot call SED.withMagnitude on this bandpass, because it does not" - " have a zeropoint. See Bandpass.withZeropoint()") + raise GalSimError("Cannot call SED.withMagnitude on this bandpass, because it does " + "not have a zeropoint. See Bandpass.withZeropoint()") current_magnitude = self.calculateMagnitude(bandpass) norm = 10**(-0.4*(target_magnitude - current_magnitude)) return self * norm @@ -771,8 +771,8 @@ def calculateMagnitude(self, bandpass): if self.dimensionless: raise GalSimSEDError("Cannot calculate magnitude of dimensionless SED.", self) if bandpass.zeropoint is None: - raise GalSimError("Cannot do this calculation for a bandpass without an assigned" - " zeropoint") + raise GalSimError("Cannot do this calculation for a bandpass without an assigned " + "zeropoint") flux = self.calculateFlux(bandpass) return -2.5 * np.log10(flux) + bandpass.zeropoint diff --git a/galsim/sensor.py b/galsim/sensor.py index 40124a37055..ce84e24ee15 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -36,6 +36,7 @@ from .table import LookupTable from .random import UniformDeviate from . import meta_data +from .errors import GalSimUndefinedBoundsError class Sensor(object): """ @@ -68,7 +69,7 @@ def accumulate(self, photons, image, orig_center=None, resume=False): @returns the total flux that fell onto the image. """ if not image.bounds.isDefined(): - raise GalSimError("Calling accumulate on image with undefined bounds") + raise GalSimUndefinedBoundsError("Calling accumulate on image with undefined bounds") return photons.addTo(image) def __repr__(self): @@ -278,7 +279,7 @@ def accumulate(self, photons, image, orig_center=PositionI(0,0), resume=False): "not match one used in the previous accumulate call.") self._last_image = image if not image.bounds.isDefined(): - raise GalSimError("Calling accumulate on image with undefined bounds") + raise GalSimUndefinedBoundsError("Calling accumulate on image with undefined bounds") return self._silicon.accumulate(photons._pa, self.rng._rng, image._image, orig_center._p, resume) diff --git a/galsim/vonkarman.py b/galsim/vonkarman.py index 56e5cf0e160..1ad9b29f12e 100644 --- a/galsim/vonkarman.py +++ b/galsim/vonkarman.py @@ -121,7 +121,7 @@ def _sbvk(self): sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux, self._scale, self._do_delta, self._gsparams._gsp) except RuntimeError as err: # pragma: no cover - # There are apparently a couple possible failure modes that can be found in the + # There are a couple possible failure modes that can be found in the # C++ layer. Turn them into GalSimErrors. raise GalSimError(std(err)) From 827c7e5d88cf15828e16c3652c82bfefeef7f24a Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 14:30:29 -0400 Subject: [PATCH 16/96] Move EnsureDir function to utilities (#755) --- galsim/config/output.py | 31 ++----------------------------- galsim/download_cosmos.py | 9 +++------ galsim/utilities.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/galsim/config/output.py b/galsim/config/output.py index f67ab553f0b..eb351409564 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -20,6 +20,8 @@ import galsim import logging +from ..utilities import EnsureDir + # This file handles building the output files according to the specifications in config['output']. # This file includes the basic functionality, but it calls out to helper functions for the # different types of output files. It includes the implementation of the default output type, @@ -375,35 +377,6 @@ def RetryIO(func, args, ntries, file_name, logger): return ret -def EnsureDir(target): - """ - Make sure the directory for the target location exists, watching for a race condition - - In particular check if the OS reported that the directory already exists when running - makedirs, which can happen if another process creates it before this one can - """ - - _ERR_FILE_EXISTS=17 - dir = os.path.dirname(target) - if dir == '': return - - exists = os.path.exists(dir) - if not exists: - try: - os.makedirs(dir) - except OSError as err: - # check if the file now exists, which can happen if some other - # process created the directory between the os.path.exists call - # above and the time of the makedirs attempt. This is OK - if err.errno != _ERR_FILE_EXISTS: - raise err - - elif exists and not os.path.isdir(dir): - raise OSError("tried to make directory '%s' " - "but a non-directory file of that " - "name already exists" % dir) - - class OutputBuilder(object): """A base class for building and writing the output objects. diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 7028c230dd3..286f8e62f04 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -27,6 +27,8 @@ except: from urllib.request import urlopen +from .utilities import EnsureDir + script_name = 'galsim_download_cosmos' def parse_args(): @@ -152,11 +154,6 @@ def query_yes_no(question, default="yes"): sys.stdout.write("Please respond with 'yes' or 'no' "\ "(or 'y' or 'n').\n") -def ensure_dir(target): - d = os.path.dirname(target) - if not os.path.exists(d): - os.makedirs(d) - def download(url, target, unpack_dir, args, logger): logger.warning('Downloading from url:\n %s',url) logger.warning('Target location is:\n %s',target) @@ -171,7 +168,7 @@ def download(url, target, unpack_dir, args, logger): logger.info("Size of %s: %d MBytes" , file_name, file_size/1024**2) # Make sure the directory we want to put this file exists. - ensure_dir(target) + EnsureDir(target) # Check if the file already exists and if it is the right size do_download = True diff --git a/galsim/utilities.py b/galsim/utilities.py index 4061bbd9945..cc200610e30 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -23,6 +23,7 @@ from future.utils import iteritems from builtins import range, object import weakref +import os import numpy as np from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning @@ -1500,3 +1501,31 @@ def __call__(self, *args): if self.c() is None : raise TypeError('Method called on dead object') return self.f(self.c(), *args) + +def EnsureDir(target): + """ + Make sure the directory for the target location exists, watching for a race condition + + In particular check if the OS reported that the directory already exists when running + makedirs, which can happen if another process creates it before this one can + """ + + _ERR_FILE_EXISTS=17 + dir = os.path.dirname(target) + if dir == '': return + + exists = os.path.exists(dir) + if not exists: + try: + os.makedirs(dir) + except OSError as err: + # check if the file now exists, which can happen if some other + # process created the directory between the os.path.exists call + # above and the time of the makedirs attempt. This is OK + if err.errno != _ERR_FILE_EXISTS: + raise err + + elif exists and not os.path.isdir(dir): + raise OSError("tried to make directory '%s' " + "but a non-directory file of that " + "name already exists" % dir) From b4fc371b339b84487cb09e5b0269453f1fd25c91 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 14:31:18 -0400 Subject: [PATCH 17/96] Use GalSimConfigError for errors processing config files (#755) --- galsim/__init__.py | 1 + galsim/config/extra.py | 14 +++--- galsim/config/extra_psf.py | 10 ++-- galsim/config/extra_truth.py | 2 +- galsim/config/gsobject.py | 26 +++++----- galsim/config/image.py | 19 +++---- galsim/config/image_scattered.py | 16 +++--- galsim/config/image_tiled.py | 18 +++---- galsim/config/input.py | 8 +-- galsim/config/input_cosmos.py | 4 +- galsim/config/input_nfw.py | 13 ++--- galsim/config/input_powerspectrum.py | 12 +++-- galsim/config/input_real.py | 4 +- galsim/config/noise.py | 18 +++---- galsim/config/output.py | 18 ++++--- galsim/config/output_datacube.py | 4 +- galsim/config/output_multifits.py | 3 +- galsim/config/process.py | 22 ++++---- galsim/config/stamp.py | 53 +++++++++++--------- galsim/config/stamp_ring.py | 11 ++-- galsim/config/value.py | 75 +++++++++++++++------------- galsim/config/value_eval.py | 17 ++++--- galsim/config/value_random.py | 20 +++++--- galsim/config/wcs.py | 8 +-- galsim/errors.py | 22 +++++++- tests/test_config_gsobject.py | 2 +- tests/test_config_image.py | 17 +++---- tests/test_config_noise.py | 8 +-- tests/test_config_output.py | 10 ++-- tests/test_config_value.py | 11 ++-- 30 files changed, 268 insertions(+), 198 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index e12c83a77d5..4ae706ba714 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -105,6 +105,7 @@ from .errors import GalSimError, GalSimRangeError, GalSimValueError from .errors import GalSimImmutableError, GalSimUndefinedBoundsError from .errors import GalSimSEDError, GalSimHSMError, GalSimIncompatibleValuesError +from .errors import GalSimConfigError, GalSimConfigValueError from .errors import GalSimWarning # Image diff --git a/galsim/config/extra.py b/galsim/config/extra.py index d25669a4b83..81111473018 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -17,7 +17,6 @@ # import os -import galsim import logging import inspect @@ -31,6 +30,8 @@ # builder classes that will perform the different processing functions. valid_extra_outputs = {} +import galsim + def SetupExtraOutput(config, logger=None): """ @@ -272,7 +273,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): # If no hdu, then probably writing to file continue if hdu <= 0 or hdu in hdus: - raise ValueError("%s hdu = %d is invalid or a duplicate."%hdu) + raise galsim.GalSimConfigValueError("hdu is invalid or a duplicate.",hdu) builder = config['extra_builder'][key] @@ -285,7 +286,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): first = len(main_data) for h in range(first,len(hdus)+first): if h not in hdus: - raise ValueError("Cannot skip hdus. No output found for hdu %d"%h) + 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 @@ -302,7 +303,8 @@ def CheckNoExtraOutputHDUs(config, output_type, logger=None): if 'hdu' in field: hdu = galsim.config.ParseValue(field,'hdu',config,int)[0] logger.error("Extra output %s requesting to write to hdu %d", key, hdu) - raise AttributeError("Output type %s cannot add extra images as HDUs"%output_type) + raise galsim.GalSimConfigError( + "Output type %s cannot add extra images as HDUs"%output_type) def GetFinalExtraOutput(key, config, main_data, logger=None): @@ -480,9 +482,9 @@ def writeHdu(self, config, base, logger): """ n = len(self.data) if n == 0: - raise galsim.GalSimError("No %s images were created."%self._extra_output_key) + raise galsim.GalSimConfigError("No %s images were created."%self._extra_output_key) elif n > 1: - raise galsim.GalSimError( + raise galsim.GalSimConfigError( "%d %s images were created, but expecting only 1."%(n,self._extra_output_key)) return self.data[0] diff --git a/galsim/config/extra_psf.py b/galsim/config/extra_psf.py index a4559cd83c5..1a51c2241f6 100644 --- a/galsim/config/extra_psf.py +++ b/galsim/config/extra_psf.py @@ -22,6 +22,7 @@ import logging # The psf extra output type builds an Image of the PSF at the same locations as the galaxies. +from .stamp import valid_draw_methods # The code the actually draws the PSF on a postage stamp. def DrawPSFStamp(psf, config, base, bounds, offset, method, logger): @@ -32,8 +33,8 @@ def DrawPSFStamp(psf, config, base, bounds, offset, method, logger): """ if 'draw_method' in config: method = galsim.config.ParseValue(config,'draw_method',base,str)[0] - if method not in ['auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb']: - raise AttributeError("Invalid draw_method: %s"%method) + if method not in valid_draw_methods: + raise galsim.GalSimConfigValueError("Invalid draw_method.", method, valid_draw_methods) else: method = 'auto' @@ -43,13 +44,14 @@ def DrawPSFStamp(psf, config, base, bounds, offset, method, logger): if 'signal_to_noise' in config: if method == 'phot': - raise NotImplementedError( + 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 AttributeError("Need to specify noise level when using psf.signal_to_noise") + 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] diff --git a/galsim/config/extra_truth.py b/galsim/config/extra_truth.py index 74a5195e1dc..365f6119d7f 100644 --- a/galsim/config/extra_truth.py +++ b/galsim/config/extra_truth.py @@ -86,7 +86,7 @@ def processStamp(self, obj_num, config, base, logger): base['obj_num']) logger.error("Types for current object = %s",repr(types)) logger.error("Expecting types = %s",repr(self.scratch['types'])) - raise galsim.GalSimError("Type mismatch found when building truth catalog.") + raise galsim.GalSimConfigError("Type mismatch found when building truth catalog.") self.scratch[obj_num] = row # The function to call at the end of building each file to finalize the truth catalog diff --git a/galsim/config/gsobject.py b/galsim/config/gsobject.py index f3303a79046..f8137f31d86 100644 --- a/galsim/config/gsobject.py +++ b/galsim/config/gsobject.py @@ -75,7 +75,7 @@ def BuildGSObject(config, key, base=None, gsparams={}, logger=None): # Get the type to be parsed. if not 'type' in param: - raise AttributeError("type attribute required in config.%s"%key) + 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. @@ -134,10 +134,9 @@ def BuildGSObject(config, key, base=None, gsparams={}, logger=None): # need to get the PSF's half_light_radius. if 'resolution' in param: if 'psf' not in base: - raise AttributeError( - "Cannot use gal.resolution if no psf is set.") + raise galsim.GalSimConfigError("Cannot use gal.resolution if no psf is set.") if 'saved_re' not in base['psf']: - raise AttributeError( + 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] @@ -145,7 +144,7 @@ def BuildGSObject(config, key, base=None, gsparams={}, logger=None): 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 AttributeError( + raise galsim.GalSimConfigError( 'Cannot specify both gal.resolution and gal.half_light_radius') param['re_from_res'] = True param['half_light_radius'] = gal_re @@ -163,7 +162,7 @@ def BuildGSObject(config, key, base=None, gsparams={}, logger=None): elif type_name in galsim.__dict__: build_func = eval("galsim."+type_name) else: - raise NotImplementedError("Unrecognised config type = %s"%type_name) + 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) @@ -247,7 +246,7 @@ def _BuildAdd(config, base, ignore, gsparams, logger): gsobjects = [] items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=Add is not a list.") + raise galsim.GalSimConfigError("items entry for type=Add is not a list.") safe = True for i in range(len(items)): @@ -260,7 +259,7 @@ def _BuildAdd(config, base, ignore, gsparams, logger): gsobjects.append(gsobject) if len(gsobjects) == 0: - raise ValueError("No valid items for type=Add") + raise galsim.GalSimConfigError("No valid items for type=Add") elif len(gsobjects) == 1: gsobject = gsobjects[0] else: @@ -301,7 +300,7 @@ def _BuildConvolve(config, base, ignore, gsparams, logger): gsobjects = [] items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=Convolve is not a 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) @@ -309,7 +308,7 @@ def _BuildConvolve(config, base, ignore, gsparams, logger): gsobjects.append(gsobject) if len(gsobjects) == 0: - raise ValueError("No valid items for type=Convolve") + raise galsim.GalSimConfigError("No valid items for type=Convolve") elif len(gsobjects) == 1: gsobject = gsobjects[0] else: @@ -335,13 +334,13 @@ def _BuildList(config, base, ignore, gsparams, logger): items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=List is not a 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 AttributeError("index %d out of bounds for List"%index) + raise galsim.GalSimConfigError("index %d out of bounds for List"%index) gsobject, safe1 = BuildGSObject(items, index, base, gsparams, logger) safe = safe and safe1 @@ -368,7 +367,8 @@ def _BuildOpticalPSF(config, base, ignore, gsparams, logger): aber_list = [0.0] * 4 # Initial 4 values are ignored. aberrations = config['aberrations'] if not isinstance(aberrations,list): - raise AttributeError("aberrations entry for config.OpticalPSF entry is not a list.") + raise galsim.GalSimConfigError( + "aberrations entry for config.OpticalPSF entry is not a list.") for i in range(len(aberrations)): value, safe1 = galsim.config.ParseValue(aberrations, i, base, float) aber_list.append(value) diff --git a/galsim/config/image.py b/galsim/config/image.py index 58fb95036b2..f1faeaa009c 100644 --- a/galsim/config/image.py +++ b/galsim/config/image.py @@ -122,7 +122,7 @@ def SetupConfigImageNum(config, image_num, obj_num, logger=None): config['image'] = {} image = config['image'] if not isinstance(image, dict): - raise AttributeError("config.image is not a dict.") + raise galsim.GalSimConfigError("config.image is not a dict.") if 'file_num' not in config: config['file_num'] = 0 @@ -131,7 +131,7 @@ def SetupConfigImageNum(config, image_num, obj_num, logger=None): image['type'] = 'Single' image_type = image['type'] if image_type not in valid_image_types: - raise AttributeError("Invalid image.type=%s."%image_type) + raise galsim.GalSimConfigValueError("Invalid image.type.", image_type, valid_image_types) # In case this hasn't been done yet. galsim.config.SetupInput(config, logger) @@ -168,12 +168,13 @@ def SetupConfigImageSize(config, xsize, ysize, logger=None): origin = 1 # default if 'index_convention' in image: convention = galsim.config.ParseValue(image,'index_convention',config,str)[0] - if convention.lower() in [ '0', 'c', 'python' ]: + if convention.lower() in ('0', 'c', 'python'): origin = 0 - elif convention.lower() in [ '1', 'fortran', 'fits' ]: + elif convention.lower() in ('1', 'fortran', 'fits'): origin = 1 else: - raise AttributeError("Unknown index_convention: %s"%convention) + raise galsim.GalSimConfigValueError("Unknown index_convention", convention, + ('0', 'c', 'python', '1', 'fortran', 'fits')) config['image_origin'] = galsim.PositionI(origin,origin) config['image_center'] = galsim.PositionD( origin + (xsize-1.)/2., origin + (ysize-1.)/2. ) @@ -285,7 +286,7 @@ def GetNObjForImage(config, image_num): image = config.get('image',{}) image_type = image.get('type','Single') if image_type not in valid_image_types: - raise AttributeError("Invalid image.type=%s."%image_type) + raise galsim.GalSimConfigValueError("Invalid image.type.", image_type, valid_image_types) return valid_image_types[image_type].getNObj(image,config,image_num) @@ -355,7 +356,7 @@ def MakeImageTasks(config, jobs, logger): image = config.get('image', {}) image_type = image.get('type', 'Single') if image_type not in valid_image_types: - raise AttributeError("Invalid image.type=%s."%image_type) + raise galsim.GalSimConfigValueError("Invalid image.type.", image_type, valid_image_types) return valid_image_types[image_type].makeTasks(image, config, jobs, logger) @@ -398,8 +399,8 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): xsize = params.get('xsize',size) ysize = params.get('ysize',size) if (xsize == 0) != (ysize == 0): - raise AttributeError( - "Both (or neither) of image.xsize and image.ysize need to be defined and != 0.") + raise galsim.GalSimConfigError( + "Both (or neither) of image.xsize and image.ysize need to be defined and != 0.") # We allow world_pos to be in config[image], but we don't want it to lead to a final_shift # in BuildStamp. To mark this, we set image_pos to (0,0) diff --git a/galsim/config/image_scattered.py b/galsim/config/image_scattered.py index 9aaabe3e8f8..5ce51f47e9a 100644 --- a/galsim/config/image_scattered.py +++ b/galsim/config/image_scattered.py @@ -55,16 +55,16 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): # Special check for the size. Either size or both xsize and ysize is required. if 'size' not in params: if 'xsize' not in params or 'ysize' not in params: - raise AttributeError( + raise galsim.GalSimConfigError( "Either size or both xsize and ysize is required for image.type=Scattered") full_xsize = params['xsize'] full_ysize = params['ysize'] else: if 'xsize' in params: - raise AttributeError( + raise galsim.GalSimConfigError( "Attributes xsize is invalid if size is set for image.type=Scattered") if 'ysize' in params: - raise AttributeError( + raise galsim.GalSimConfigError( "Attributes ysize is invalid if size is set for image.type=Scattered") full_xsize = params['size'] full_ysize = params['size'] @@ -72,8 +72,8 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): # 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 ValueError( - "Unable to reconcile required image xsize and ysize with provided "+ + 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 @@ -101,7 +101,8 @@ def buildImage(self, config, base, image_num, obj_num, logger): base['current_image'] = full_image if 'image_pos' in config and 'world_pos' in config: - raise AttributeError("Both image_pos and world_pos specified for Scattered image.") + raise galsim.GalSimConfigError( + "Both image_pos and world_pos specified for Scattered image.") if 'image_pos' not in config and 'world_pos' not in config: xmin = base['image_origin'].x @@ -190,7 +191,8 @@ def getNObj(self, config, base, image_num): if 'nobjects' not in config: nobj = galsim.config.ProcessInputNObjects(base) if nobj is None: - raise AttributeError("Attribute nobjects is required for image.type = Scattered") + 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 diff --git a/galsim/config/image_tiled.py b/galsim/config/image_tiled.py index 86a5b0627e2..3a50ff634b0 100644 --- a/galsim/config/image_tiled.py +++ b/galsim/config/image_tiled.py @@ -58,7 +58,7 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): self.stamp_ysize = params.get('stamp_ysize',stamp_size) if (self.stamp_xsize == 0) or (self.stamp_ysize == 0): - raise AttributeError( + raise galsim.GalSimConfigError( "Both image.stamp_xsize and image.stamp_ysize need to be defined and != 0.") border = params.get("border",0) @@ -82,12 +82,12 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): # 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 ValueError( - "Unable to reconcile required image xsize and ysize with provided "+ - "nx_tiles=%d, ny_tiles=%d, "%(self.nx_tiles,self.ny_tiles) + - "xborder=%d, yborder=%d\n"%(self.xborder,self.yborder) + - "Calculated full_size = (%d,%d) "%(full_xsize,full_ysize)+ - "!= required (%d,%d)."%(base['image_force_xsize'],base['image_force_ysize'])) + raise galsim.GalSimConfigError( + "Unable to reconcile required image xsize and ysize with provided " + "nx_tiles=%d, ny_tiles=%d, xborder=%d, yborder=%d\n" + "Calculated full_size = (%d,%d) != required (%d,%d)."%( + self.nx_tiles, self.ny_tiles, self.xborder, self.yborder, + full_xsize, full_ysize, base['image_force_xsize'],base['image_force_ysize'])) return full_xsize, full_ysize @@ -133,7 +133,7 @@ def buildImage(self, config, base, image_num, obj_num, logger): rng = galsim.config.GetRNG(config, base, logger, 'TiledImage, order = '+order) galsim.random.permute(rng, ix_list, iy_list) else: - raise ValueError("Invalid order. Must be row, column, or random") + 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. @@ -219,7 +219,7 @@ def getNObj(self, config, base, image_num): base['image_num'] = image_num if 'nx_tiles' not in config or 'ny_tiles' not in config: - raise AttributeError( + raise galsim.GalSimConfigError( "Attributes nx_tiles and ny_tiles are required for image.type = Tiled") nx = galsim.config.ParseValue(config,'nx_tiles',base,int)[0] ny = galsim.config.ParseValue(config,'ny_tiles',base,int)[0] diff --git a/galsim/config/input.py b/galsim/config/input.py index b1115a97acb..81b11cd3f02 100644 --- a/galsim/config/input.py +++ b/galsim/config/input.py @@ -293,7 +293,8 @@ def GetInputObj(input_type, config, base, param_name): error messages). """ if 'input_objs' not in base or input_type not in base['input_objs']: - raise ValueError("No input %s available for type = %s"%(input_type,param_name)) + 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] @@ -301,9 +302,10 @@ def GetInputObj(input_type, config, base, param_name): num = 0 if num < 0: - raise ValueError("Invalid num < 0 supplied for %s: num = %d"%(param_name,num)) + raise galsim.GalSimConfigValueError("Invalid num < 0 supplied for %s."%param_name, num) if num >= len(base['input_objs'][input_type]): - raise ValueError("Invalid num supplied for %s (too large): num = %d"%(param_name,num)) + raise galsim.GalSimConfigValueError("Invalid num supplied for %s (too large)"%param_name, + num) return base['input_objs'][input_type][num] diff --git a/galsim/config/input_cosmos.py b/galsim/config/input_cosmos.py index 2eabd816cff..411e8d0eb19 100644 --- a/galsim/config/input_cosmos.py +++ b/galsim/config/input_cosmos.py @@ -110,8 +110,8 @@ def _BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger): # SetDefaultIndex. index = kwargs['index'] if index >= cosmos_cat.getNObjects(): - raise IndexError( - "%s index has gone past the number of entries in the catalog"%index) + raise galsim.GalSimConfigError( + "index=%s has gone past the number of entries in the COSMOSCatalog"%index) logger.debug('obj %d: COSMOSGalaxy kwargs = %s',base.get('obj_num',0),kwargs) diff --git a/galsim/config/input_nfw.py b/galsim/config/input_nfw.py index 19fdf13bca7..6bcec94f087 100644 --- a/galsim/config/input_nfw.py +++ b/galsim/config/input_nfw.py @@ -44,11 +44,11 @@ def _GenerateFromNFWHaloShear(config, base, value_type): logger = nfw_halo.logger if 'world_pos' not in base: - raise ValueError("NFWHaloShear requested, but no position defined.") + 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 ValueError("NFWHaloShear requested, but no gal.redshift defined.") + 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) @@ -77,11 +77,12 @@ def _GenerateFromNFWHaloMagnification(config, base, value_type): logger = nfw_halo.logger if 'world_pos' not in base: - raise ValueError("NFWHaloMagnification requested, but no position defined.") + 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 ValueError("NFWHaloMagnification requested, but no gal.redshift defined.") + 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 } @@ -89,8 +90,8 @@ def _GenerateFromNFWHaloMagnification(config, base, value_type): max_mu = kwargs.get('max_mu', 25.) if not max_mu > 0.: - raise ValueError( - "Invalid max_mu=%f (must be > 0) for type = NFWHaloMagnification"%max_mu) + 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: diff --git a/galsim/config/input_powerspectrum.py b/galsim/config/input_powerspectrum.py index 0cca4ad5d71..4bbd6a1e611 100644 --- a/galsim/config/input_powerspectrum.py +++ b/galsim/config/input_powerspectrum.py @@ -109,7 +109,8 @@ def setupImage(self, input_obj, config, base, logger=None): scale = base['wcs'].maxLinearScale(base['image_center']) grid_spacing = grid_size * scale else: - raise AttributeError("power_spectrum.grid_spacing required for non-tiled images") + raise galsim.GalSimConfigError( + "power_spectrum.grid_spacing required for non-tiled images") if 'ngrid' in config: ngrid = galsim.config.ParseValue(config, 'ngrid', base, float)[0] @@ -183,7 +184,7 @@ def _GenerateFromPowerSpectrumShear(config, base, value_type): logger = power_spectrum.logger if 'world_pos' not in base: - raise ValueError("PowerSpectrumShear requested, but no position defined.") + raise galsim.GalSimConfigError("PowerSpectrumShear requested, but no position defined.") pos = base['world_pos'] # There aren't any parameters for this, so just make sure num is the only (optional) @@ -223,7 +224,8 @@ def _GenerateFromPowerSpectrumMagnification(config, base, value_type): logger = power_spectrum.logger if 'world_pos' not in base: - raise ValueError("PowerSpectrumMagnification requested, but no position defined.") + raise galsim.GalSimConfigError( + "PowerSpectrumMagnification requested, but no position defined.") pos = base['world_pos'] opt = { 'max_mu' : float, 'num' : int } @@ -240,8 +242,8 @@ def _GenerateFromPowerSpectrumMagnification(config, base, value_type): max_mu = kwargs.get('max_mu', 25.) if not max_mu > 0.: - raise ValueError( - "Invalid max_mu=%f (must be > 0) for type = PowerSpectrumMagnification"%max_mu) + 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. '%( diff --git a/galsim/config/input_real.py b/galsim/config/input_real.py index 46f94ac84e8..5fc5efbb39f 100644 --- a/galsim/config/input_real.py +++ b/galsim/config/input_real.py @@ -55,8 +55,8 @@ def _BuildRealGalaxy(config, base, ignore, gsparams, logger, param_name='RealGal if 'index' in kwargs: index = kwargs['index'] if index >= real_cat.getNObjects(): - raise IndexError( - "%s index has gone past the number of entries in the catalog"%index) + raise galsim.GalSimConfigError( + "index=%s has gone past the number of entries in the RealGalaxyCatalog"%index) kwargs['real_galaxy_catalog'] = real_cat logger.debug('obj %d: %s kwargs = %s',base.get('obj_num',0),param_name,kwargs) diff --git a/galsim/config/noise.py b/galsim/config/noise.py index 829185432a0..f4cb1379928 100644 --- a/galsim/config/noise.py +++ b/galsim/config/noise.py @@ -65,7 +65,7 @@ def AddNoise(config, im, current_var=0., logger=None): else: noise_type = 'Poisson' # Default is Poisson if noise_type not in valid_noise_types: - raise AttributeError("Invalid type %s for noise"%noise_type) + 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 @@ -94,14 +94,14 @@ def CalculateNoiseVariance(config): """ noise = config['image']['noise'] if not isinstance(noise, dict): - raise AttributeError("image.noise is not a dict.") + raise galsim.GalSimConfigError("image.noise is not a dict.") if 'type' in noise: noise_type = noise['type'] else: noise_type = 'Poisson' # Default is Poisson if noise_type not in valid_noise_types: - raise AttributeError("Invalid type %s for noise"%noise_type) + 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' @@ -139,7 +139,7 @@ def AddNoiseVariance(config, im, include_obj_var=False, logger=None): else: noise_type = 'Poisson' # Default is Poisson if noise_type not in valid_noise_types: - raise AttributeError("Invalid type %s for noise"%noise_type) + 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' @@ -159,7 +159,7 @@ def GetSky(config, base, logger=None): logger = galsim.config.LoggerWrapper(logger) if 'sky_level' in config: if 'sky_level_pixel' in config: - raise AttributeError("Cannot specify both sky_level and sky_level_pixel") + raise galsim.GalSimConfigError("Cannot specify both sky_level and sky_level_pixel") sky_level = galsim.config.ParseValue(config,'sky_level',base,float)[0] logger.debug('image %d, obj %d: sky_level = %f', base.get('image_num',0),base.get('obj_num',0), sky_level) @@ -268,7 +268,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): 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.GalSimError( + raise galsim.GalSimConfigError( "Whitening already added more noise than the requested Gaussian noise.") var -= current_var @@ -324,7 +324,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): else: test = (total_sky < current_var) if test: - raise galsim.GalSimError( + raise galsim.GalSimConfigError( "Whitening already added more noise than the requested Poisson noise.") total_sky -= current_var extra_sky -= current_var @@ -428,7 +428,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): target_var, current_var) test = target_var < current_var if test: - raise galsim.GalSimError( + raise galsim.GalSimConfigError( "Whitening already added more noise than the requested CCD noise.") if read_noise_var_adu >= current_var: # First try to take away from the read_noise, since this one is actually Gaussian. @@ -540,7 +540,7 @@ def addNoise(self, config, base, im, rng, current_var, draw_method, logger): 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.GalSimError( + raise galsim.GalSimConfigError( "Whitening already added more noise than the requested COSMOS noise.") cn -= galsim.UncorrelatedNoise(current_var, rng=rng, wcs=cn.wcs) diff --git a/galsim/config/output.py b/galsim/config/output.py index eb351409564..a3cf4369549 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -119,7 +119,8 @@ def done_func(logger, proc, k, result, t2): file_num, file_name = info[k] file_name2, t = result # This is the t for which 0 means the file was skipped. if file_name2 != file_name: - raise galsim.GalSimError("Files seem to be out of sync. %s != %s",file_name, file_name2) + raise galsim.GalSimConfigError("Files seem to be out of sync. %s != %s", + file_name, file_name2) if t != 0 and logger: if proc is None: s0 = '' else: s0 = '%s: '%proc @@ -262,7 +263,8 @@ def GetNFiles(config): output = config.get('output',{}) output_type = output.get('type','Fits') if output_type not in valid_output_types: - raise AttributeError("Invalid output.type=%s."%output_type) + raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, + valid_output_types) return valid_output_types[output_type].getNFiles(output, config) @@ -279,7 +281,8 @@ def GetNImagesForFile(config, file_num): output = config.get('output',{}) output_type = output.get('type','Fits') if output_type not in valid_output_types: - raise AttributeError("Invalid output.type=%s."%output_type) + raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, + valid_output_types) return valid_output_types[output_type].getNImages(output, config, file_num) @@ -297,7 +300,8 @@ def GetNObjForFile(config, file_num, image_num): output = config.get('output',{}) output_type = output.get('type','Fits') if output_type not in valid_output_types: - raise AttributeError("Invalid output.type=%s."%output_type) + raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, + valid_output_types) return valid_output_types[output_type].getNObjPerImage(output, config, file_num, image_num) @@ -338,7 +342,8 @@ def SetupConfigFileNum(config, file_num, image_num, obj_num, logger=None): # Check that the type is valid output_type = config['output']['type'] if output_type not in valid_output_types: - raise AttributeError("Invalid output.type=%s."%output_type) + raise galsim.GalSimConfigValueError("Invalid output.type.", output_type, + valid_output_types) def SetDefaultExt(config, default_ext): @@ -420,7 +425,8 @@ def getFilename(self, config, base, logger): # 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 AttributeError("No file_name specified and unable to generate it automatically.") + 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: diff --git a/galsim/config/output_datacube.py b/galsim/config/output_datacube.py index ad77c0db018..4dff3baa37f 100644 --- a/galsim/config/output_datacube.py +++ b/galsim/config/output_datacube.py @@ -21,6 +21,7 @@ import logging from .output import OutputBuilder + class DataCubeBuilder(OutputBuilder): """Builder class for constructing and writing DataCube output types. """ @@ -95,7 +96,8 @@ def getNImages(self, config, base, file_num): if nimages: config['nimages'] = nimages if 'nimages' not in config: - raise AttributeError("Attribute output.nimages is required for output.type = MultiFits") + raise galsim.GalSimConfigError( + "Attribute output.nimages is required for output.type = MultiFits") return galsim.config.ParseValue(config,'nimages',base,int)[0] def writeFile(self, data, file_name, config, base, logger): diff --git a/galsim/config/output_multifits.py b/galsim/config/output_multifits.py index 082b231ee10..10f61b5f38a 100644 --- a/galsim/config/output_multifits.py +++ b/galsim/config/output_multifits.py @@ -67,7 +67,8 @@ def getNImages(self, config, base, file_num): if nimages: config['nimages'] = nimages if 'nimages' not in config: - raise AttributeError("Attribute output.nimages is required for output.type = MultiFits") + raise galsim.GalSimConfigError( + "Attribute output.nimages is required for output.type = MultiFits") return galsim.config.ParseValue(config,'nimages',base,int)[0] diff --git a/galsim/config/process.py b/galsim/config/process.py index a7c6ab35d39..385b761e2f3 100644 --- a/galsim/config/process.py +++ b/galsim/config/process.py @@ -604,7 +604,8 @@ def ParseExtendedKey(config, key): 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 ValueError("Unable to parse extended key %s. Field %s is invalid."%(key,k)) + raise galsim.GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) return d, k def GetFromConfig(config, key): @@ -623,7 +624,8 @@ def GetFromConfig(config, key): try: value = d[k] except Exception as e: - raise ValueError("Unable to parse extended key %s. Field %s is invalid."%(key,k)) + raise galsim.GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) return value def SetInConfig(config, key, value): @@ -646,7 +648,8 @@ def SetInConfig(config, key, value): try: d[k] = value except Exception as e: - raise ValueError("Unable to parse extended key %s. Field %s is invalid."%(key,k)) + raise galsim.GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) def UpdateConfig(config, new_params): @@ -742,11 +745,11 @@ def Process(config, logger=None, njobs=1, job=1, new_params=None, except_abort=F logger = LoggerWrapper(logger) import pprint if njobs < 1: - raise ValueError("Invalid number of jobs %d"%njobs) + raise galsim.GalSimValueError("Invalid number of jobs",njobs) if job < 1: - raise ValueError("Invalid job number %d. Must be >= 1."%job) + raise galsim.GalSimValueError("Invalid job number. Must be >= 1.",job) if job > njobs: - raise ValueError("Invalid job number %d. Must be <= njobs (%d)"%(job,njobs)) + raise galsim.GalSimValueError("Invalid job number. Must be <= njobs (%d)"%(njobs),job) # First thing to do is deep copy the input config to make sure we don't modify the original. config = CopyConfig(config) @@ -1029,7 +1032,7 @@ def GetIndex(config, base, is_sequence=False): if 'index_key' in config: index_key = config['index_key'] if index_key not in valid_index_keys: - raise AttributeError("Invalid index_key=%s."%index_key) + raise galsim.GalSimConfigValueError("Invalid index_key.", index_key, valid_index_keys) else: index_key = base.get('index_key','obj_num') if index_key == 'obj_num' and is_sequence: @@ -1064,9 +1067,10 @@ def GetRNG(config, base, logger=None, tag=''): if 'rng_num' in config: rng_num = config['rng_num'] if int(rng_num) != rng_num: - raise ValueError("rng_num must be an integer") + raise galsim.GalSimConfigValueError("rng_num must be an integer", rng_num) if not (index_key + '_rngs') in base: - raise AttributeError("rng_num is only allowed when image.random_seed is a list") + raise galsim.GalSimConfigError( + "rng_num is only allowed when image.random_seed is a list") rng = base.get(index_key + '_rngs', None)[int(rng_num)] else: rng = base.get(index_key + '_rng', None) diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index cfeb2aedee2..5985d201372 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -144,7 +144,7 @@ def SetupConfigObjNum(config, obj_num, logger=None): config['stamp'] = {} stamp = config['stamp'] if not isinstance(stamp, dict): - raise AttributeError("config.stamp is not a dict.") + raise galsim.GalSimConfigError("config.stamp is not a dict.") if 'type' not in stamp: stamp['type'] = 'Basic' @@ -264,6 +264,8 @@ def SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos, logger=None 'wmult', 'n_photons', 'max_extra_noise', 'poisson_flux', 'skip', 'reject', 'min_flux_frac', 'min_snr', 'max_snr'] +valid_draw_methods = ('auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb') + 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 @@ -284,7 +286,7 @@ def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): stamp = config['stamp'] stamp_type = stamp['type'] if stamp_type not in valid_stamp_types: - raise AttributeError("Invalid stamp.type=%s."%stamp_type) + 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. @@ -363,8 +365,9 @@ def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): if not skip: method = galsim.config.ParseValue(stamp,'draw_method',config,str)[0] - if method not in ['auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb']: - raise AttributeError("Invalid draw_method: %s"%method) + 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: @@ -403,9 +406,9 @@ def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): builder.reset(config, logger) continue else: - raise galsim.GalSimError( - "Rejected an object %d times. If this is expected, "%ntries+ - "you should specify a larger stamp.retry_failures.") + 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) @@ -509,7 +512,7 @@ def DrawBasic(prof, image, method, offset, config, base, logger, **kwargs): max_extra_noise = None if 'n_photons' in config and 'n_photons' not in kwargs: if method != 'phot': - raise AttributeError('n_photons is invalid with method != phot') + raise galsim.GalSimConfigError('n_photons is invalid with method != phot') if 'max_extra_noise' in config: logger.warning( "Both 'max_extra_noise' and 'n_photons' are set in config dict, "+ @@ -518,22 +521,22 @@ def DrawBasic(prof, image, method, offset, config, base, logger, **kwargs): elif 'max_extra_noise' in config: max_extra_noise = galsim.config.ParseValue(config, 'max_extra_noise', base, float)[0] if method != 'phot' and max_extra_noise is not None: - raise AttributeError('max_extra_noise is invalid with method != phot') + raise galsim.GalSimConfigError('max_extra_noise is invalid with method != phot') if 'poisson_flux' in config and 'poisson_flux' not in kwargs: if method != 'phot': - raise AttributeError('poisson_flux is invalid with method != phot') + raise galsim.GalSimConfigError('poisson_flux is invalid with method != phot') kwargs['poisson_flux'] = galsim.config.ParseValue(config, 'poisson_flux', base, bool)[0] if max_extra_noise is not None and 'max_extra_noise' not in kwargs: if max_extra_noise < 0.: - raise ValueError("image.max_extra_noise cannot be negative") + raise galsim.GalSimConfigError("image.max_extra_noise cannot be negative") if 'image' in base and 'noise' in base['image']: noise_var = galsim.config.CalculateNoiseVariance(base) else: - raise AttributeError("Need to specify noise level when using max_extra_noise") + raise galsim.GalSimConfigError("Need to specify noise level when using max_extra_noise") if noise_var < 0.: - raise ValueError("noise_var calculated to be < 0.") + raise galsim.GalSimConfigError("noise_var calculated to be < 0.") max_extra_noise *= noise_var kwargs['max_extra_noise'] = max_extra_noise @@ -587,7 +590,7 @@ def ParseWorldPos(config, param_name, base, logger): if isinstance(param, dict): value_type = galsim.config.value.valid_value_types[param.get('type','XY')][1][0] if value_type not in [galsim.PositionD, galsim.CelestialCoord]: - raise AttributeError('Invalid type %s for %s',param.get('type',None),param_name) + raise galsim.GalSimConfigError('Invalid %s.type'%(param_name), param.get('type',None)) return galsim.config.ParseValue(config, param_name, base, value_type)[0] else: value_type = (galsim.CelestialCoord if type(param) == galsim.CelestialCoord @@ -693,8 +696,9 @@ def buildProfile(self, config, base, psf, gsparams, logger): elif 'gal' in base or 'psf' in base: return None else: - raise AttributeError("At least one of gal or psf must be specified in config. " + - "If you really don't want any object, use gal type = None.") + 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 makeStamp(self, config, base, xsize, ysize, logger): """Make the initial empty postage stamp image, if possible. @@ -796,7 +800,8 @@ def whiten(self, prof, image, config, base, logger): noise = base['image']['noise'] if 'whiten' in noise: if 'symmetrize' in noise: - raise AttributeError('Only one of whiten or symmetrize is allowed') + raise galsim.GalSimConfigError( + 'Only one of whiten or symmetrize is allowed') whiten, safe = galsim.config.ParseValue(noise, 'whiten', base, bool) # In case the galaxy was cached, update the rng rng = galsim.config.GetRNG(noise, base, logger, "whiten") @@ -829,13 +834,13 @@ def getSNRScale(self, image, config, base, logger): return 1. if 'flux' in base[key]: - raise AttributeError( + 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 AttributeError( + 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] @@ -899,8 +904,9 @@ def reject(self, config, base, prof, psf, image, logger): return True if 'min_flux_frac' in config: if not isinstance(prof, galsim.GSObject): - raise ValueError("Cannot apply min_flux_frac for stamp types that do not use "+ - "a single GSObject profile.") + 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] @@ -912,8 +918,9 @@ def reject(self, config, base, prof, psf, image, logger): return True if 'min_snr' in config or 'max_snr' in config: if not isinstance(prof, galsim.GSObject): - raise ValueError("Cannot apply min_snr for stamp types that do not use "+ - "a single GSObject profile.") + 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) diff --git a/galsim/config/stamp_ring.py b/galsim/config/stamp_ring.py index 2ca5cfb9350..809e140b5fa 100644 --- a/galsim/config/stamp_ring.py +++ b/galsim/config/stamp_ring.py @@ -55,7 +55,8 @@ def setup(self, config, base, xsize, ysize, ignore, logger): num = params['num'] if num <= 0: - raise ValueError("Attribute num for gal.type == Ring must be > 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) @@ -90,20 +91,20 @@ def buildProfile(self, config, base, psf, gsparams, logger): 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 AttributeError("index %d out of bounds for Ring"%index) + 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 AttributeError( + 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.GalSimError( + 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] @@ -154,7 +155,7 @@ def makeTasks(self, config, base, jobs, logger): @returns a list of tasks """ if 'num' not in config: - raise AttributeError("Attribute num is required for type = Ring") + raise galsim.GalSimConfigError("Attribute num is required for type = Ring") num = galsim.config.ParseValue(config, 'num', base, int)[0] ntot = len(jobs) tasks = [ [ (jobs[j], j) for j in range(k,min(k+num,ntot)) ] for k in range(0, ntot, num) ] diff --git a/galsim/config/value.py b/galsim/config/value.py index 715343c47ad..d8ae824ce19 100644 --- a/galsim/config/value.py +++ b/galsim/config/value.py @@ -73,30 +73,30 @@ def ParseValue(config, key, base, value_type): if use_current: if (value_type is not None and cvalue_type is not None and cvalue_type != value_type): - raise ValueError( - "Attempt to parse %s multiple times with different value types:"%key + - " %s and %s"%(value_type, cvalue_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 AttributeError( - "%s.type attribute required in config for non-constant parameter %s."%(key,key)) + 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 AttributeError( - "Unrecognized type = %s specified for parameter %s"%(type_name,key)) + 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 AttributeError( - "Invalid value_type = %s specified for parameter %s with type = %s."%( - value_type, key, type_name)) + 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 @@ -163,7 +163,10 @@ def ParseValue(config, key, base, value_type): # 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.) - val = value_type(param) + 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 @@ -289,9 +292,10 @@ def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): get[key] = value_type else: # pragma: no cover if 'type' in config: - raise AttributeError("Attribute %s is required for type = %s"%(key,config['type'])) + raise galsim.GalSimConfigError( + "Attribute %s is required for type = %s"%(key,config['type'])) else: - raise AttributeError("Attribute %s is required"%key) + raise galsim.GalSimConfigError("Attribute %s is required"%key) # Check optional items: for (key, value_type) in opt.items(): @@ -307,18 +311,19 @@ def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): count += 1 if count > 1: # pragma: no cover if 'type' in config: - raise AttributeError( + raise galsim.GalSimConfigError( "Only one of the attributes %s is allowed for type = %s"%( s.keys(),config['type'])) else: - raise AttributeError("Only one of the attributes %s is allowed"%s.keys()) + raise galsim.GalSimConfigError( + "Only one of the attributes %s is allowed"%s.keys()) get[key] = value_type if count == 0: # pragma: no cover if 'type' in config: - raise AttributeError( + raise galsim.GalSimConfigError( "One of the attributes %s is required for type = %s"%(s.keys(),config['type'])) else: - raise AttributeError("One of the attributes %s is required"%s.keys()) + raise galsim.GalSimConfigError("One of the attributes %s is required"%s.keys()) # Check that there aren't any extra keys in config aside from a few we expect: valid_keys += ignore @@ -326,7 +331,7 @@ def CheckAllParams(config, req={}, opt={}, single=[], 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 AttributeError("Unexpected attribute %s found"%(key)) + raise galsim.GalSimConfigError("Unexpected attribute %s found"%(key)) config['_get'] = get return get @@ -366,7 +371,7 @@ def _GetAngleValue(param): unit = galsim.AngleUnit.from_name(unit) return galsim.Angle(value, unit) except (TypeError, AttributeError) as e: # pragma: no cover - raise AttributeError("Unable to parse %s as an Angle. Caught %s"%(param,e)) + raise galsim.GalSimConfigError("Unable to parse %s as an Angle. Caught %s"%(param,e)) def _GetPositionValue(param): @@ -381,7 +386,8 @@ def _GetPositionValue(param): x = float(x.strip()) y = float(y.strip()) except (TypeError, AttributeError) as e: # pragma: no cover - raise AttributeError("Unable to parse %s as a PositionD. Caught %s"%(param,e)) + raise galsim.GalSimConfigError( + "Unable to parse %s as a PositionD. Caught %s"%(param,e)) return galsim.PositionD(x,y) @@ -398,13 +404,14 @@ def _GetBoolValue(param): val = bool(int(param)) return val except (TypeError, AttributeError) as e: # pragma: no cover - raise AttributeError("Unable to parse %s as a bool. Caught %s"%(param,e)) + raise galsim.GalSimConfigError( + "Unable to parse %s as a bool. Caught %s"%(param,e)) else: try: val = bool(param) return val except (TypeError, AttributeError) as e: # pragma: no cover - raise AttributeError("Unable to parse %s as a bool. Caught %s"%(param,e)) + raise galsim.GalSimConfigError("Unable to parse %s as a bool. Caught %s"%(param,e)) @@ -526,10 +533,10 @@ def _GenerateFromSequence(config, base, value_type): nitems = kwargs.get('nitems',None) if repeat <= 0: - raise ValueError( - "Invalid repeat=%d (must be > 0) for type = Sequence"%repeat) + raise galsim.GalSimConfigValueError( + "Invalid repeat for type = Sequence (must be > 0)", repeat) if last is not None and nitems is not None: - raise AttributeError( + 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) @@ -612,7 +619,7 @@ def _GenerateFromFormattedStr(config, base, value_type): continue token = token.lstrip('0123456789lLh') # ignore field size, and long/short specification if len(token) == 0: - raise ValueError("Unable to parse '%s' as a valid format string"%format) + 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': @@ -620,12 +627,12 @@ def _GenerateFromFormattedStr(config, base, value_type): elif token[0].lower() in 'rs': val_types.append(str) else: - raise ValueError("Unable to parse '%s' as a valid format string"%format) + raise galsim.GalSimConfigError("Unable to parse %r as a valid format string"%format) if len(val_types) != len(items): - raise ValueError( - "Number of items for FormatStr (%d) does not match number expected from "%len(items)+ - "format string (%d)"%len(val_types)) + 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]) @@ -646,14 +653,14 @@ def _GenerateFromList(config, base, value_type): CheckAllParams(config, req=req, opt=opt) items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=List is not a 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. SetDefaultIndex(config, len(items)) index, safe = ParseValue(config, 'index', base, int) if index < 0 or index >= len(items): - raise AttributeError("index %d out of bounds for type=List"%index) + raise galsim.GalSimConfigError("index %d out of bounds for type=List"%index) val, safe1 = ParseValue(items, index, base, value_type) safe = safe and safe1 #print(base['obj_num'],'List index = %d, val = %s'%(index,val)) @@ -667,7 +674,7 @@ def _GenerateFromSum(config, base, value_type): CheckAllParams(config, req=req) items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=List is not a list.") + raise galsim.GalSimConfigError("items entry for type=List is not a list.") sum, safe = ParseValue(items, 0, base, value_type) @@ -695,7 +702,7 @@ def _GenerateFromCurrent(config, base, value_type): try: return EvaluateCurrentValue(k, d, base, value_type) except ValueError as e: # pragma: no cover - raise ValueError("%s\nError generating Current value with key = %s"%(e,k)) + raise galsim.GalSimConfigError("%s\nError generating Current value with key = %s"%(e,k)) def RegisterValueType(type_name, gen_func, valid_types, input_type=None): diff --git a/galsim/config/value_eval.py b/galsim/config/value_eval.py index 0a016b63a40..7f7ba516ee9 100644 --- a/galsim/config/value_eval.py +++ b/galsim/config/value_eval.py @@ -25,7 +25,7 @@ def _type_by_letter(key): if len(key) < 2: - raise AttributeError("Invalid user-defined variable %r"%key) + raise galsim.GalSimConfigError("Invalid user-defined variable %r"%key) if key[0] == 'f': return float elif key[0] == 'i': @@ -45,7 +45,8 @@ def _type_by_letter(key): elif key[0] == 'x': return None else: - raise AttributeError("Invalid Eval variable: %s (starts with an invalid letter)"%key) + raise galsim.GalSimConfigError( + "Invalid Eval variable: %s (starts with an invalid letter)"%key) eval_base_variables = [ 'image_pos', 'world_pos', 'image_center', 'image_origin', 'image_bounds', 'image_xsize', 'image_ysize', 'stamp_xsize', 'stamp_ysize', 'pixel_scale', @@ -91,7 +92,8 @@ def _GenerateFromEval(config, base, value_type): gdict = base['eval_gdict'] if 'str' not in config: - raise AttributeError("Attribute str is required for type = %s"%(config['type'])) + raise galsim.GalSimConfigError( + "Attribute str is required for type = %s"%(config['type'])) string = config['str'] # Turn any "Current" items indicated with an @ sign into regular variables. @@ -121,7 +123,7 @@ def _GenerateFromEval(config, base, value_type): if 'eval_variables' in base: #print('found eval_variables = ',galsim.config.CleanConfig(base['eval_variables'])) if not isinstance(base['eval_variables'],dict): - raise AttributeError("eval_variables must be a dict") + raise galsim.GalSimConfigError("eval_variables must be a dict") for key in base['eval_variables']: # Only add variables that appear in the string. if _isWordInString(key[1:],string) and key[1:] not in params: @@ -152,7 +154,8 @@ def _GenerateFromEval(config, base, value_type): except KeyboardInterrupt: raise except Exception as e: # pragma: no cover - raise ValueError("Unable to evaluate string %r as a %s\n"%(string,value_type) + str(e)) + raise galsim.GalSimConfigError( + "Unable to evaluate string %r as a %s\n%r"%(string, value_type, e)) # Always need to evaluate any parameters to pass to the function opt = {} @@ -175,8 +178,8 @@ def _GenerateFromEval(config, base, value_type): except KeyboardInterrupt: raise except Exception as e: # pragma: no cover - raise ValueError("Unable to evaluate string %r as a %s\n"%(config['str'],value_type) + - str(e)) + raise galsim.GalSimConfigError( + "Unable to evaluate string %r as a %s\n%r"%(config['str'],value_type, e)) # Register this as a valid value type diff --git a/galsim/config/value_random.py b/galsim/config/value_random.py index 8d9a27e343c..71f34f6cf99 100644 --- a/galsim/config/value_random.py +++ b/galsim/config/value_random.py @@ -158,7 +158,8 @@ def _GenerateFromRandomBinomial(config, base, value_type): N = kwargs.get('N',1) p = kwargs.get('p',0.5) if value_type is bool and N != 1: - raise ValueError("N must = 1 for type = RandomBinomial used in bool context") + raise galsim.GalSimConfigValueError( + "N must = 1 for type = RandomBinomial used in bool context", N) dev = galsim.BinomialDeviate(rng,N=N,p=p) val = dev() @@ -230,9 +231,11 @@ def _GenerateFromRandomDistribution(config, base, value_type): # 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 AttributeError("Both x and f must be provided for type=RandomDistribution") + raise galsim.GalSimConfigError( + "Both x and f must be provided for type=RandomDistribution") if 'function' in kwargs: - raise AttributeError("Cannot provide function with x,f for type=RandomDistribution") + 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) @@ -242,9 +245,11 @@ def _GenerateFromRandomDistribution(config, base, value_type): interpolant=interpolant) else: if 'function' not in kwargs: - raise AttributeError("function or x,f must be provided for type=RandomDistribution") + raise galsim.GalSimConfigError( + "function or x,f must be provided for type=RandomDistribution") if 'x_log' in config or 'f_log' in config: - raise AttributeError("x_log, f_log are invalid with function for type=RandomDistribution") + 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 @@ -281,8 +286,9 @@ def _GenerateFromRandomCircle(config, base, value_type): min_rsq = inner_radius**2 if min_rsq >= max_rsq: - raise ValueError("inner_radius (%f) must be less than radius (%f) for type=RandomCircle"%( - inner_radius, radius)) + raise galsim.GalSimConfigValueError( + "inner_radius must be less than radius (%f) for type=RandomCircle"%(radius), + inner_radius) # Emulate a do-while loop while True: diff --git a/galsim/config/wcs.py b/galsim/config/wcs.py index 9209777f64c..6785c992a18 100644 --- a/galsim/config/wcs.py +++ b/galsim/config/wcs.py @@ -57,7 +57,7 @@ def BuildWCS(config, key, base, logger=None): 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 ValueError("wcs must be either a BaseWCS or a dict") + raise galsim.GalSimConfigError("wcs must be either a BaseWCS or a dict") elif 'type' in param: wcs_type = param['type'] else: @@ -83,7 +83,7 @@ def BuildWCS(config, key, base, logger=None): param['origin'] = origin if wcs_type not in valid_wcs_types: - raise AttributeError("Invalid image.wcs.type=%s."%wcs_type) + 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) @@ -247,14 +247,14 @@ def buildWCS(self, config, base, logger): galsim.config.CheckAllParams(config, req=req, opt=opt) items = config['items'] if not isinstance(items,list): - raise AttributeError("items entry for type=List is not a 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 AttributeError("index %d out of bounds for wcs type=List"%index) + raise galsim.GalSimConfigError("index %d out of bounds for wcs type=List"%index) return BuildWCS(items, index, base) def RegisterWCSType(wcs_type, builder, input_type=None): diff --git a/galsim/errors.py b/galsim/errors.py index ca83fd85bfa..9b234260268 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -89,8 +89,7 @@ def __init__(self, message, value, allowed_values=None): message += " not in {0!s}".format(allowed_values) super().__init__(message) self.value = value - self.min = min - self.max = max + self.allowed_values = allowed_values class GalSimRangeError(GalSimError, ValueError): @@ -182,6 +181,25 @@ def __init__(self, message, image): self.image = image +class GalSimConfigError(GalSimError, ValueError): + """A GalSim-specific exception class indicating some kind of failure processing a + configuration file. + """ + pass + + +class GalSimConfigValueError(GalSimValueError, GalSimConfigError): + """A GalSim-specific exception class indicating that a config entry has an invalid value. + + Attrubutes: + + value = the invalid value + allowed_values = a list of allowed values if appropriate (may be None) + """ + # Should use GalSimValueError init according to mro. + pass + + class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index c908448e799..ded208fd3ac 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -837,7 +837,7 @@ def test_cosmosgalaxy(): gsobject_compare(gal3a, gal3b, conv=conv) config['obj_num'] = 3 - with assert_raises(IndexError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildGSObject(config, 'gal4') # One more test: make sure that if we specified from the start not to use real galaxies, that diff --git a/tests/test_config_image.py b/tests/test_config_image.py index d57d1817b0f..bdd3efd546e 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -217,7 +217,7 @@ def test_phot(): # So without the noise field, it will raise an exception. del config['image']['n_photons'] del config['stamp']['n_photons'] - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildImage(config) # Using this much extra noise with a sky noise variance of 50 cuts the number of photons @@ -349,9 +349,8 @@ def HighN(config, base, value_type): assert "obj 1: Skipping because field skip=True" in cl.output assert "obj 1: Caught SkipThisObject: e = None" in cl.output assert "Skipping object 1" in cl.output - assert "Object 0: Caught exception 105 index has gone past the number of entries" in cl.output - assert ("Object 0: Caught exception inner_radius (5.369661) must be less than radius "+ - "(3.931733) for type=RandomCircle") in cl.output + assert "Object 0: Caught exception index=105 has gone past the number of entries" in cl.output + assert "Object 0: Caught exception inner_radius must be less than radius (3.931733)" in cl.output assert "Object 0: Caught exception Unable to evaluate string 'math.sqrt(x)'" in cl.output assert "obj 0: reject evaluated to True" in cl.output assert "Object 0: Rejecting this object and rebuilding" in cl.output @@ -372,12 +371,12 @@ def HighN(config, base, value_type): # If we lower the number of retries, we'll max out and abort the image config['stamp']['retry_failures'] = 10 galsim.config.RemoveCurrent(config) - with assert_raises((ValueError, IndexError, galsim.GalSimError)): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamps(nimages, config, do_noise=False) try: with CaptureLog() as cl: galsim.config.BuildStamps(nimages, config, do_noise=False, logger=cl.logger) - except (ValueError,IndexError,galsim.GalSimError): + except (galsim.GalSimConfigError): pass #print(cl.output) assert "Object 0: Too many exceptions/rejections for this object. Aborting." in cl.output @@ -746,11 +745,11 @@ def test_scattered(): # Check error message for missing nobjects del config['image']['nobjects'] - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildImage(config) # Also if there is an input field that doesn't have nobj capability config['input'] = { 'dict' : { 'dir' : 'config_input', 'file_name' : 'dict.p' } } - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildImage(config) # However, an input field that does have nobj will return something for nobjects. # This catalog has 3 rows, so equivalent to nobjects = 3 @@ -1291,7 +1290,7 @@ def test_wcs(): wcs = galsim.config.BuildWCS(config, 'wcs', config) assert wcs == galsim.PixelScale(1.0) - with assert_raises(ValueError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildWCS(config['image'], 'invalid', config) @timer diff --git a/tests/test_config_noise.py b/tests/test_config_noise.py index b3a6cda71e5..490576c3ad5 100644 --- a/tests/test_config_noise.py +++ b/tests/test_config_noise.py @@ -604,7 +604,7 @@ def test_whiten(): # If whitening already added too much noise, raise an exception config['image']['noise']['variance'] = 1.e-5 - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) # 2. Poisson noise @@ -652,7 +652,7 @@ def test_whiten(): np.testing.assert_almost_equal(im3d.array, im3c.array, decimal=5) config['image']['noise']['sky_level_pixel'] = 1.e-5 - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) # 3. CCDNoise @@ -705,7 +705,7 @@ def test_whiten(): config['image']['noise']['sky_level_pixel'] = 1.e-5 config['image']['noise']['read_noise'] = 0 - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) # 4. COSMOSNoise @@ -729,7 +729,7 @@ def test_whiten(): config['image']['noise']['variance'] = 1.e-5 del config['_current_cn_tag'] - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) diff --git a/tests/test_config_output.py b/tests/test_config_output.py index 26e8aaa21fd..c036209d7d9 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -183,11 +183,11 @@ def test_multifits(): # Check error message for missing nimages del config['output']['nimages'] - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(config) # Also if there is an input field that doesn't have nobj capability config['input'] = { 'dict' : { 'dir' : 'config_input', 'file_name' : 'dict.p' } } - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(config) # However, an input field that does have nobj will return something for nobjects. @@ -251,11 +251,11 @@ def test_datacube(): # Check error message for missing nimages del config['output']['nimages'] - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(config) # Also if there is an input field that doesn't have nobj capability config['input'] = { 'dict' : { 'dir' : 'config_input', 'file_name' : 'dict.p' } } - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(config) # However, an input field that does have nobj will return something for nobjects. @@ -273,7 +273,7 @@ def test_datacube(): config['output']['weight'] = { 'hdu' : 1 } config['output']['badpix'] = { 'file_name' : 'output/test_datacube_bp.fits' } config['image']['noise'] = { 'type' : 'Gaussian', 'variance' : 0.1 } - with assert_raises(AttributeError): + with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(config) # But if both weight and badpix are files, then it should work. diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 890af82d1e2..8b0cd2c7b01 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -401,10 +401,13 @@ def test_float_value(): assert "Warning: position (1000.000000,2000.000000) not within the bounds" in cl.output np.testing.assert_almost_equal(ps2c, 1.) - # Should raise an AttributeError if there is no type in the dict - assert_raises(AttributeError, galsim.config.ParseValue, config, 'no_type', config, float) - assert_raises(AttributeError, galsim.config.ParseValue, config, 'bad_key', config, float) - assert_raises(ValueError, galsim.config.ParseValue, config, 'bad_value', config, float) + # Should raise a GalSimConfigError if there is no type in the dict + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'no_type', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad_key', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad_value', config, float) @timer From ea04155d31d75202b04edc4c04fdb32cb65d2833 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 17:24:18 -0400 Subject: [PATCH 18/96] Add basic tests of error object functionality (#755) --- galsim/__init__.py | 4 +- galsim/errors.py | 113 +++++++++++----- tests/galsim_test_helpers.py | 8 +- tests/test_errors.py | 241 +++++++++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 36 deletions(-) create mode 100644 tests/test_errors.py diff --git a/galsim/__init__.py b/galsim/__init__.py index 4ae706ba714..51e1ee0fc53 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -103,8 +103,8 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError -from .errors import GalSimImmutableError, GalSimUndefinedBoundsError -from .errors import GalSimSEDError, GalSimHSMError, GalSimIncompatibleValuesError +from .errors import GalSimBoundsError, GalSimUndefinedBoundsError, GalSimImmutableError +from .errors import GalSimIncompatibleValuesError, GalSimSEDError, GalSimHSMError from .errors import GalSimConfigError, GalSimConfigValueError from .errors import GalSimWarning diff --git a/galsim/errors.py b/galsim/errors.py index 9b234260268..5b49495fa4d 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -72,7 +72,10 @@ class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. """ - pass + # Minimal version of these to make GalSimError reprable and picklable. + def __repr__(self): return 'galsim.GalSimError(%r)'%(str(self)) + def __eq__(self, other): return repr(self) == repr(other) + def __hash__(self): return hash(repr(self)) class GalSimValueError(GalSimError, ValueError): @@ -84,12 +87,19 @@ class GalSimValueError(GalSimError, ValueError): allowed_values = a list of allowed values if appropriate (may be None) """ def __init__(self, message, value, allowed_values=None): + self.message = message + self.value = value + self.allowed_values = allowed_values + message += " Value {0!s}".format(value) if allowed_values: message += " not in {0!s}".format(allowed_values) super().__init__(message) - self.value = value - self.allowed_values = allowed_values + + def __repr__(self): + return 'galsim.GalSimValueError(%r,%r,%r)'%(self.message, self.value, self.allowed_values) + def __reduce__(self): # Need to override this whenever constructor take extra params + return GalSimValueError, (self.message, self.value, self.allowed_values) class GalSimRangeError(GalSimError, ValueError): @@ -103,12 +113,19 @@ class GalSimRangeError(GalSimError, ValueError): max = the maximum allowed value (may be None) """ def __init__(self, message, value, min, max=None): - message += " Value {0!s} not in range [{1!s}, {2!s}].".format(value, min, max) - super().__init__(message) + self.message = message self.value = value self.min = min self.max = max + message += " Value {0!s} not in range [{1!s}, {2!s}]".format(value, min, max) + super().__init__(message) + + def __repr__(self): + return 'galsim.GalSimRangeError(%r,%r,%r,%r)'%(self.message, self.value, self.min, self.max) + def __reduce__(self): + return GalSimRangeError, (self.message, self.value, self.min, self.max) + class GalSimBoundsError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input position is @@ -120,17 +137,45 @@ class GalSimBoundsError(GalSimError, ValueError): bounds = the bounds in which it was expected to fall """ def __init__(self, message, pos, bounds): - message += " Position {0!s} not in bounds {1!s}.".format(pos, bounds) - super().__init__(message) + self.message = message self.pos = pos self.bounds = bounds + message += " {0!s} not in {1!s}".format(pos, bounds) + super().__init__(message) + + def __repr__(self): + return 'galsim.GalSimBoundsError(%r,%r,%r)'%(self.message, self.pos, self.bounds) + def __reduce__(self): + return GalSimBoundsError, (self.message, self.pos, self.bounds) + class GalSimUndefinedBoundsError(GalSimError): """A GalSim-specific exception class indicating an attempt to access the range of bounds that have not yet been defined. """ - pass + def __repr__(self): + return 'galsim.GalSimUndefinedBoundsError(%r)'%(str(self)) + + +class GalSimImmutableError(GalSimError): + """A GalSim-specific exception class indicating an attempt to modify an immutable image. + + Attrubutes: + + image = the image that the user attempted to modify + """ + def __init__(self, message, image): + self.message = message + self.image = image + + message += " Image: {0!s}".format(image) + super().__init__(message) + + def __repr__(self): + return 'galsim.GalSimImmutableError(%r,%r)'%(self.message, self.image) + def __reduce__(self): + return GalSimImmutableError, (self.message, self.image) class GalSimIncompatibleValuesError(GalSimError, ValueError, TypeError): @@ -141,11 +186,18 @@ class GalSimIncompatibleValuesError(GalSimError, ValueError, TypeError): values = a dict of {name : value} giving the values that in combination are invalid. """ - def __init__(self, message, **kwargs): - self.values = kwargs + def __init__(self, message, values={}, **kwargs): + self.message = message + self.values = dict(values, **kwargs) + message += " Values {0!s}".format(self.values) super().__init__(message) + def __repr__(self): + return 'galsim.GalSimIncompatibleValuesError(%r,%r)'%(self.message, self.values) + def __reduce__(self): + return GalSimIncompatibleValuesError, (self.message, self.values) + class GalSimSEDError(GalSimError, TypeError): """A GalSim-specific exception class indicating an attempt to do something invalid for the @@ -157,35 +209,31 @@ class GalSimSEDError(GalSimError, TypeError): sed = the invalid SED """ def __init__(self, message, sed): - message += " SED: {0!s}.".format(sed) - super().__init__(message) + self.message = message self.sed = sed + message += " SED: {0!s}".format(sed) + super().__init__(message) -class GalSimHSMError(GalSimError): - """A GalSim-specific exception class indicating some kind of failure of the HSM algorithms - """ - pass - - -class GalSimImmutableError(GalSimError): - """A GalSim-specific exception class indicating an attempt to modify an immutable image. + def __repr__(self): + return 'galsim.GalSimSEDError(%r,%r)'%(self.message, self.sed) + def __reduce__(self): + return GalSimSEDError, (self.message, self.sed) - Attrubutes: - image = the image that the user attempted to modify +class GalSimHSMError(GalSimError): + """A GalSim-specific exception class indicating some kind of failure of the HSM algorithms """ - def __init__(self, message, image): - message += " Image: {0!s}".format(image) - super().__init__(message) - self.image = image + def __repr__(self): + return 'galsim.GalSimHSMError(%r)'%(str(self)) class GalSimConfigError(GalSimError, ValueError): """A GalSim-specific exception class indicating some kind of failure processing a configuration file. """ - pass + def __repr__(self): + return 'galsim.GalSimConfigError(%r)'%(str(self)) class GalSimConfigValueError(GalSimValueError, GalSimConfigError): @@ -196,11 +244,16 @@ class GalSimConfigValueError(GalSimValueError, GalSimConfigError): value = the invalid value allowed_values = a list of allowed values if appropriate (may be None) """ - # Should use GalSimValueError init according to mro. - pass + def __repr__(self): + return 'galsim.GalSimConfigValueError(%r,%r,%r)'%( + self.message, self.value, self.allowed_values) + def __reduce__(self): + return GalSimConfigValueError, (self.message, self.value, self.allowed_values) class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ - pass + def __repr__(self): return 'galsim.GalSimWarning(%r)'%(str(self)) + def __eq__(self, other): return repr(self) == repr(other) + def __hash__(self): return hash(repr(self)) diff --git a/tests/galsim_test_helpers.py b/tests/galsim_test_helpers.py index fb7105b8f28..daaf0b0d5cc 100644 --- a/tests/galsim_test_helpers.py +++ b/tests/galsim_test_helpers.py @@ -455,11 +455,11 @@ def do_pickle(obj1, func = lambda x : x, irreprable=False): #print(repr(obj1)) with galsim.utilities.printoptions(precision=18, threshold=np.inf): obj5 = eval(repr(obj1)) - print('obj1 = ',repr(obj1)) - print('obj5 = ',repr(obj5)) + #print('obj1 = ',repr(obj1)) + #print('obj5 = ',repr(obj5)) f5 = func(obj5) - print('f1 = ',f1) - print('f5 = ',f5) + #print('f1 = ',f1) + #print('f5 = ',f5) assert f5 == f1, "func(obj1) = %r\nfunc(obj5) = %r"%(f1, f5) else: # Even if we're not actually doing the test, still make the repr to check for syntax errors. diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 00000000000..cd72557e488 --- /dev/null +++ b/tests/test_errors.py @@ -0,0 +1,241 @@ +# Copyright (c) 2012-2018 by the GalSim developers team on GitHub +# https://github.com/GalSim-developers +# +# This file is part of GalSim: The modular galaxy image simulation toolkit. +# https://github.com/GalSim-developers/GalSim +# +# GalSim is free software: redistribution and use in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions, and the disclaimer given in the accompanying LICENSE +# file. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the disclaimer given in the documentation +# and/or other materials provided with the distribution. +# + +from __future__ import print_function + +import galsim +from galsim_test_helpers import * + +@timer +def test_galsim_error(): + """Test basic usage of GalSimError + """ + err = galsim.GalSimError("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, RuntimeError) + do_pickle(err) + + +@timer +def test_galsim_value_error(): + """Test basic usage of GalSimValueError + """ + value = 2.3 + err = galsim.GalSimValueError("Test", value) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3" + assert err.value == value + assert err.allowed_values == None + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + do_pickle(err) + + err = galsim.GalSimValueError("Test", value, (0,1,2)) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3 not in (0, 1, 2)" + assert err.value == value + assert err.allowed_values == (0,1,2) + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + do_pickle(err) + + +@timer +def test_galsim_range_error(): + """Test basic usage of GalSimRangeError + """ + value = 2.3 + err = galsim.GalSimRangeError("Test", value, 0, 1) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3 not in range [0, 1]" + assert err.value == value + assert err.min == 0 + assert err.max == 1 + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + do_pickle(err) + + err = galsim.GalSimRangeError("Test", value, 10) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3 not in range [10, None]" + assert err.value == value + assert err.min == 10 + assert err.max == None + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + do_pickle(err) + + +@timer +def test_galsim_bounds_error(): + """Test basic usage of GalSimBoundsError + """ + pos = galsim.PositionI(0,0) + bounds = galsim.BoundsI(1,10,1,10) + err = galsim.GalSimBoundsError("Test", pos, bounds) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test galsim.PositionI(0,0) not in galsim.BoundsI(1,10,1,10)" + assert err.pos == pos + assert err.bounds == bounds + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + do_pickle(err) + + +@timer +def test_galsim_undefined_bounds_error(): + """Test basic usage of GalSimUndefinedBoundsError + """ + err = galsim.GalSimUndefinedBoundsError("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, galsim.GalSimError) + do_pickle(err) + + +@timer +def test_galsim_immutable_error(): + """Test basic usage of GalSimImmutableError + """ + im = galsim.ImageD(np.array([[0]]), make_const=True) + err = galsim.GalSimImmutableError("Test", im) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Image: galsim.Image(bounds=galsim.BoundsI(1,1,1,1), wcs=None, dtype=numpy.float64)" + assert err.image == im + assert isinstance(err, galsim.GalSimError) + do_pickle(err) + + +@timer +def test_galsim_incompatible_values_error(): + """Test basic usage of GalSimIncompatibleValuesError + """ + err = galsim.GalSimIncompatibleValuesError("Test", a=1, b=2) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Values {'a': 1, 'b': 2}" + assert err.values == dict(a=1, b=2) + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, ValueError) + assert isinstance(err, TypeError) + do_pickle(err) + + +@timer +def test_galsim_sed_error(): + """Test basic usage of GalSimSEDError + """ + sed = galsim.SED('1', wave_type='nm', flux_type='fphotons') + err = galsim.GalSimSEDError("Test", sed) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test SED: galsim.SED('1', redshift=0.0)" + assert err.sed == sed + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, TypeError) + do_pickle(err) + + +@timer +def test_galsim_hsm_error(): + """Test basic usage of GalSimHSMError + """ + err = galsim.GalSimHSMError("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, galsim.GalSimError) + do_pickle(err) + + +@timer +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) + + +@timer +def test_galsim_config_value_error(): + """Test basic usage of GalSimConfigValueError + """ + value = 2.3 + err = galsim.GalSimConfigValueError("Test", value) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3" + assert err.value == value + assert err.allowed_values == None + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, galsim.GalSimConfigError) + assert isinstance(err, ValueError) + do_pickle(err) + + err = galsim.GalSimConfigValueError("Test", value, (0,1,2)) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Value 2.3 not in (0, 1, 2)" + assert err.value == value + assert err.allowed_values == (0,1,2) + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, galsim.GalSimConfigError) + assert isinstance(err, ValueError) + do_pickle(err) + + +@timer +def test_galsim_warning(): + """Test basic usage of GalSimWarning + """ + err = galsim.GalSimWarning("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, UserWarning) + do_pickle(err) + + +if __name__ == "__main__": + test_galsim_error() + test_galsim_value_error() + test_galsim_range_error() + test_galsim_bounds_error() + test_galsim_undefined_bounds_error() + test_galsim_immutable_error() + test_galsim_incompatible_values_error() + test_galsim_sed_error() + test_galsim_hsm_error() + test_galsim_config_error() + test_galsim_config_value_error() + test_galsim_warning() + From dc4e6253e0d2bc71428e7e26b6df35c3808db930 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 17:38:07 -0400 Subject: [PATCH 19/96] Move GalSimDeprecationWarning to errors.py (#755) --- galsim/__init__.py | 6 +++--- galsim/deprecated/__init__.py | 10 +--------- galsim/errors.py | 9 +++++++++ tests/test_errors.py | 14 +++++++++++++- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index 51e1ee0fc53..ad4fc0aeb02 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -106,7 +106,7 @@ from .errors import GalSimBoundsError, GalSimUndefinedBoundsError, GalSimImmutableError from .errors import GalSimIncompatibleValuesError, GalSimSEDError, GalSimHSMError from .errors import GalSimConfigError, GalSimConfigValueError -from .errors import GalSimWarning +from .errors import GalSimWarning, GalSimDeprecationWarning # Image from .image import Image, ImageS, ImageI, ImageF, ImageD, ImageCF, ImageCD, ImageUS, ImageUI, _Image @@ -175,8 +175,8 @@ from .sensor import Sensor, SiliconSensor from . import detectors # Everything here is a method of Image, so nothing to import by name. -# Deprecation warning class -from .deprecated import GalSimDeprecationWarning +# Deprecated functionality +from . import deprecated # Packages we intentionally keep separate. E.g. requires galsim.fits.read(...) from . import fits diff --git a/galsim/deprecated/__init__.py b/galsim/deprecated/__init__.py index ae359f8e69a..8c62870a1a3 100644 --- a/galsim/deprecated/__init__.py +++ b/galsim/deprecated/__init__.py @@ -16,15 +16,7 @@ # and/or other materials provided with the distribution. # -from ..errors import GalSimWarning - -# Note: By default python2.7 ignores DeprecationWarnings. Apparently they are really -# for python system deprecations. GalSim deprecations are thus only subclassed from -# GalSimWarning, not DeprecationWarning. The former are a subclass of UserWarning, -# which are not ignored by default. -class GalSimDeprecationWarning(GalSimWarning): - def __init__(self, s): - super(GalSimDeprecationWarning, self).__init__(self, s) +from ..errors import GalSimDeprecationWarning def depr(f, v, s1, s2=None): """A helper function for emitting a GalSimDeprecationWarning. diff --git a/galsim/errors.py b/galsim/errors.py index 5b49495fa4d..fac5b37a439 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -257,3 +257,12 @@ class GalSimWarning(UserWarning): def __repr__(self): return 'galsim.GalSimWarning(%r)'%(str(self)) def __eq__(self, other): return repr(self) == repr(other) def __hash__(self): return hash(repr(self)) + + +# Note: By default python2.7 ignores DeprecationWarnings. Apparently they are really +# for python system deprecations. GalSim deprecations are thus only subclassed from +# GalSimWarning, not DeprecationWarning. +class GalSimDeprecationWarning(GalSimWarning): + """A GalSim-specific warning class used for deprecation warnings. + """ + def __repr__(self): return 'galsim.GalSimDeprecationWarning(%r)'%(str(self)) diff --git a/tests/test_errors.py b/tests/test_errors.py index cd72557e488..18db93148f4 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -225,6 +225,18 @@ def test_galsim_warning(): do_pickle(err) +@timer +def test_galsim_deprecation_warning(): + """Test basic usage of GalSimDeprecationWarning + """ + err = galsim.GalSimDeprecationWarning("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, UserWarning) + do_pickle(err) + + if __name__ == "__main__": test_galsim_error() test_galsim_value_error() @@ -238,4 +250,4 @@ def test_galsim_warning(): test_galsim_config_error() test_galsim_config_value_error() test_galsim_warning() - + test_galsim_deprecation_warning() From 093c4526404fa6c09f94b822699095c2e9ce3305 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 18:36:16 -0400 Subject: [PATCH 20/96] Update exceptions in des module (#755) --- galsim/des/des_meds.py | 35 ++++++++++++++++++++--------------- galsim/des/des_psfex.py | 12 +++++++----- galsim/des/des_shapelet.py | 9 +++++---- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index 2dec557fe1a..90edab33fff 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -78,18 +78,19 @@ def __init__(self, images, weight=None, badpix=None, seg=None, psf=None, if not isinstance(images,list): raise TypeError('images should be a list') if len(images) == 0: - raise ValueError('no cutouts in this object') + raise galsim.GalSimValueError('no cutouts in this object', images) # Check that the box sizes are valid for i in range(len(images)): s = images[i].array.shape if s[0] != s[1]: - raise ValueError('Array shape %s is invalid. Must be square'%(str(s))) + raise galsim.GalSimValueError('Array shape %s is invalid. Must be square'%(str(s)), + images[i]) if s[0] not in BOX_SIZES: - raise ValueError('Array shape %s is invalid. Size must be in %s'%( - str(s),str(BOX_SIZES))) + raise galsim.GalSimValueError('Array shape %s is invalid. Size must be in %s'%( + str(s),str(BOX_SIZES)), images[i]) if i > 0 and s != images[0].array.shape: - raise ValueError('Images must all be the same shape') + raise galsim.GalSimValueError('Images must all be the same shape', images) # The others are optional, but if given, make sure they are ok. for lst, name, isim in [ (weight, 'weight', True), (badpix, 'badpix', True), @@ -98,31 +99,34 @@ def __init__(self, images, weight=None, badpix=None, seg=None, psf=None, if not isinstance(lst,list): raise TypeError('%s should be a list'%name) if len(lst) != len(images): - raise ValueError('%s is the wrong length'%name) + raise galsim.GalSimValueError('%s is the wrong length'%name, lst) if isim: for i in range(len(images)): im1 = lst[i] im2 = images[i] if (im1.array.shape != im2.array.shape): - raise ValueError("%s[%d] has the wrong shape."%(name, i)) + raise galsim.GalSimValueError( + "%s[%d] has the wrong shape."%(name, i), im1) # The PSF images don't have to be the same shape as the main images. # But make sure all psf images are square and the same shape if psf is not None: s = psf[i].array.shape if s[0] != s[1]: - raise ValueError('PSF array shape %s is invalid. Must be square'%(str(s))) + raise galsim.GalSimValueError( + 'PSF array shape %s is invalid. Must be square'%(str(s)), psf[i]) if s[0] not in BOX_SIZES: - raise ValueError('PSF array shape %s is invalid. Size must be in %s'%( - str(s),str(BOX_SIZES))) + raise galsim.GalSimValueError( + 'PSF array shape %s is invalid. Size must be in %s'%( + str(s),str(BOX_SIZES)), psf[i]) if i > 0 and s != psf[0].array.shape: - raise ValueError('PSF images must all be the same shape') + raise galsim.GalSimValueError('PSF images must all be the same shape', psf[i]) # Check that wcs are Uniform and convert them to AffineTransforms in case they aren't. if wcs is not None: for i in range(len(wcs)): if not isinstance(wcs[i], galsim.wcs.UniformWCS): - raise TypeError('wcs list should contain UniformWCS objects') + raise galsim.GalSimValueError('wcs list should contain UniformWCS objects', wcs) elif not isinstance(wcs[i], galsim.AffineTransform): wcs[i] = wcs[i].affine() @@ -281,9 +285,9 @@ def WriteMEDS(obj_list, file_name, clobber=True): # check if we are running out of memory if sys.getsizeof(vec) > MAX_MEMORY: # pragma: no cover - raise MemoryError( + raise galsim.GalSimError( 'Running out of memory > %1.0fGB '%MAX_MEMORY/1.e9 + - '- you can increase the limit by changing MAX_MEMORY') + '- you can increase the limit by changing galsim.des_meds.MAX_MEMORY') # update the start rows fields in the catalog cat['start_row'].append(start_rows) @@ -455,7 +459,8 @@ def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger if 'image' in base and 'type' in base['image']: image_type = base['image']['type'] if image_type != 'Single': - raise AttibuteError("MEDS files are not compatible with image type %s."%image_type) + raise galsim.GalSimConfigError( + "MEDS files are not compatible with image type %s."%image_type) req = { 'nobjects' : int , 'nstamps_per_object' : int } ignore += [ 'file_name', 'dir', 'nfiles' ] diff --git a/galsim/des/des_psfex.py b/galsim/des/des_psfex.py index 1a018a42f55..eb800ce3373 100644 --- a/galsim/des/des_psfex.py +++ b/galsim/des/des_psfex.py @@ -28,10 +28,11 @@ """ from past.builtins import basestring +import os +import numpy as np import galsim import galsim.config -import numpy as np class DES_PSFEx(object): """Class that handles DES files describing interpolated principal component images @@ -107,15 +108,16 @@ def __init__(self, file_name, image_file_name=None, wcs=None, dir=None): if dir: if not isinstance(file_name, basestring): - raise ValueError("Cannot provide dir and an HDU instance") - import os + raise TypeError("file_name must be a string") file_name = os.path.join(dir,file_name) if image_file_name is not None: image_file_name = os.path.join(dir,image_file_name) self.file_name = file_name if image_file_name: if wcs is not None: - raise AttributeError("Cannot provide both image_file_name and wcs") + raise galsim.GalSimIncompatibleValuesError( + "Cannot provide both image_file_name and wcs", + image_file_name=image_file_name, wcs=wcs) header = galsim.FitsHeader(file_name=image_file_name) wcs, origin = galsim.wcs.readFromFitsHeader(header) self.wcs = wcs @@ -338,7 +340,7 @@ def BuildDES_PSFEx(config, base, ignore, gsparams, logger): elif 'image_pos' in base: image_pos = base['image_pos'] else: - raise ValueError("DES_PSFEx requested, but no image_pos defined in base.") + raise galsim.GalSimConfigError("DES_PSFEx requested, but no image_pos defined in base.") # Convert gsparams from a dict to an actual GSParams object if gsparams: gsparams = galsim.GSParams(**gsparams) diff --git a/galsim/des/des_shapelet.py b/galsim/des/des_shapelet.py index 0965e883ad3..499c690308f 100644 --- a/galsim/des/des_shapelet.py +++ b/galsim/des/des_shapelet.py @@ -85,8 +85,8 @@ def __init__(self, file_name, dir=None, file_type=None): else: # pragma: no cover file_type = 'ASCII' file_type = file_type.upper() - if file_type not in ['FITS', 'ASCII']: - raise ValueError("file_type must be either FITS or ASCII if specified.") + if file_type not in ('FITS', 'ASCII'): + raise galsim.GalSimValueError("Invalid file_type.", file_type, ('FITS', 'ASCII')) if file_type == 'FITS': self.read_fits() @@ -192,7 +192,8 @@ def getB(self, pos): """Get the B vector as a numpy array at position pos """ if not self.bounds.includes(pos): - raise IndexError("position in DES_Shapelet.getPSF is out of bounds") + raise galsim.GalSimBoundsError("position in DES_Shapelet.getPSF is out of bounds", + pos, self.bounds) Px = self._definePxy(pos.x,self.bounds.xmin,self.bounds.xmax) Py = self._definePxy(pos.y,self.bounds.ymin,self.bounds.ymax) @@ -246,7 +247,7 @@ def BuildDES_Shapelet(config, base, ignore, gsparams, logger): elif 'image_pos' in base: image_pos = base['image_pos'] else: - raise ValueError("DES_Shapelet requested, but no image_pos defined in base.") + raise galsim.GalSimConfigError("DES_Shapelet requested, but no image_pos defined in base.") # Convert gsparams from a dict to an actual GSParams object if gsparams: gsparams = galsim.GSParams(**gsparams) From f1f50773a5a14d51168da4c446c6f6293ba3f3a0 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 19:17:19 -0400 Subject: [PATCH 21/96] Update errors in wfirst module (#755) --- galsim/wfirst/__init__.py | 3 +-- galsim/wfirst/wfirst_backgrounds.py | 17 +++++++-------- galsim/wfirst/wfirst_bandpass.py | 2 +- galsim/wfirst/wfirst_detectors.py | 9 ++++---- galsim/wfirst/wfirst_psfs.py | 32 ++++++++++++++--------------- galsim/wfirst/wfirst_wcs.py | 9 ++++---- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/galsim/wfirst/__init__.py b/galsim/wfirst/__init__.py index 66a1697b37a..356e957c11d 100644 --- a/galsim/wfirst/__init__.py +++ b/galsim/wfirst/__init__.py @@ -262,8 +262,7 @@ def _parse_SCAs(SCAs): SCAs = [SCAs] # Then check for reasonable values. if min(SCAs) <= 0 or max(SCAs) > galsim.wfirst.n_sca: - raise ValueError( - "Invalid SCA! Indices must be positive and <=%d."%galsim.wfirst.n_sca) + raise galsim.GalSimRangeError("Invalid SCA.", SCAs, 1, galsim.wfirst.n_sca) # Check for uniqueness. If not unique, make it unique. SCAs = list(set(SCAs)) else: diff --git a/galsim/wfirst/wfirst_backgrounds.py b/galsim/wfirst/wfirst_backgrounds.py index 11bfbe66bf4..fe31d46509e 100644 --- a/galsim/wfirst/wfirst_backgrounds.py +++ b/galsim/wfirst/wfirst_backgrounds.py @@ -77,8 +77,8 @@ def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): """ # Check for cached sky level information for this filter. If not, raise exception if not hasattr(bandpass, '_sky_level'): - raise galsim.GalSimError("Only bandpasses returned from galsim.wfirst.getBandpasses() are" - " allowed here!") + raise galsim.GalSimError("Only bandpasses returned from galsim.wfirst.getBandpasses() are " + "allowed here!") # Check for proper type for position, and extract the ecliptic coordinates. if world_pos is None: @@ -87,7 +87,7 @@ def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): ecliptic_lon = 90.*galsim.degrees else: if not isinstance(world_pos, galsim.CelestialCoord): - raise ValueError("Position (world_pos) must be supplied as a CelestialCoord!") + raise TypeError("world_pos must be supplied as a CelestialCoord.") if date is not None: epoch = date.year ecliptic_lon, ecliptic_lat = world_pos.ecliptic(epoch=epoch, date=date) @@ -121,14 +121,15 @@ def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): ilon = int(xlon) xlat -= ilat xlon -= ilon - sky_val = ( s[ilat, ilon] * (1.-xlat)*(1.-xlon) + \ - s[ilat, ilon+1] * (1.-xlat)*xlon + \ - s[ilat+1, ilon] * xlat*(1.-xlon) + \ - s[ilat+1, ilon+1] * xlat*xlon) + sky_val = (s[ilat, ilon] * (1.-xlat)*(1.-xlon) + + s[ilat, ilon+1] * (1.-xlat)*xlon + + s[ilat+1, ilon] * xlat*(1.-xlon) + + s[ilat+1, ilon+1] * xlat*xlon) # If the result is too large, then raise an exception: we should not look at this position! if sky_val > max_sky: - raise ValueError("Position (world_pos) is too close to sun! Would not observe here.") + raise galsim.GalSimValueError("world_pos is too close to sun. Would not observe here.", + world_pos) # Now, convert to the right units, and return. (See docstring for explanation.) # First, multiply by the effective collecting area in m^2. diff --git a/galsim/wfirst/wfirst_bandpass.py b/galsim/wfirst/wfirst_bandpass.py index adf0b8eaf77..bb5e239ef68 100644 --- a/galsim/wfirst/wfirst_bandpass.py +++ b/galsim/wfirst/wfirst_bandpass.py @@ -116,7 +116,7 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, **kwargs): if key in thin_kwargs: tmp_thin_dict[key] = kwargs.pop(key) if len(kwargs) != 0: - raise ValueError("Unknown kwargs: %s"%(' '.join(kwargs.keys()))) + raise TypeError("Unknown kwargs: %s"%(' '.join(kwargs.keys()))) # Set up a dictionary. bandpass_dict = {} diff --git a/galsim/wfirst/wfirst_detectors.py b/galsim/wfirst/wfirst_detectors.py index 97a129d2ffa..2c30418709f 100644 --- a/galsim/wfirst/wfirst_detectors.py +++ b/galsim/wfirst/wfirst_detectors.py @@ -97,15 +97,16 @@ def applyPersistence(img, prev_exposures): recent exposure being the first element. """.format(ncoeff=galsim.wfirst.persistence_coefficients) if not hasattr(prev_exposures,'__iter__'): - raise TypeError("In wfirst.applyPersistence, 'prev_exposures' must be a list of Image" - " instances") + raise TypeError("In wfirst.applyPersistence, prev_exposures must be a list of Image " + "instances") n_exp = min(len(prev_exposures),len(galsim.wfirst.persistence_coefficients)) if n_exp > len(galsim.wfirst.persistence_coefficients): import warnings warnings.warn("More than {ncoeff} Image instances were passed to" - " galsim.wfirst.applyPersistence routine. Ignoring the earlier" - " exposures".format(ncoeff=galsim.wfirst.persistence_coefficients)) + "galsim.wfirst.applyPersistence routine. Ignoring the earlier" + "exposures".format(ncoeff=galsim.wfirst.persistence_coefficients), + galsim.GalSimWarning) img.applyPersistence(prev_exposures[:n_exp],galsim.wfirst.persistence_coefficients[:n_exp]) diff --git a/galsim/wfirst/wfirst_psfs.py b/galsim/wfirst/wfirst_psfs.py index a242fe8eea8..257cdf06ba3 100644 --- a/galsim/wfirst/wfirst_psfs.py +++ b/galsim/wfirst/wfirst_psfs.py @@ -185,20 +185,19 @@ def getPSF(SCAs=None, approximate_struts=False, n_waves=None, extra_aberrations= blue_limit, red_limit = _find_limits(default_bandpass_list, bandpass_dict) else: if not isinstance(wavelength_limits, tuple): - raise ValueError("Wavelength limits must be entered as a tuple!") + raise TypeError("Wavelength limits must be entered as a tuple!") blue_limit, red_limit = wavelength_limits if red_limit <= blue_limit: - raise ValueError("Wavelength limits must have red_limit > blue_limit." - "Input: blue limit=%f, red limit=%f nanometers"% - (blue_limit, red_limit)) + raise GalSimIncompatibleValuesError( + "Wavelength limits must have red_limit > blue_limit.", + blue_limit=blue_limit, red_limit=red_limit) else: if isinstance(wavelength, galsim.Bandpass): wavelength_nm = wavelength.effective_wavelength elif isinstance(wavelength, float): wavelength_nm = wavelength else: - raise TypeError("Keyword 'wavelength' should either be a Bandpass, float," - " or None.") + raise TypeError("wavelength should either be a Bandpass, float, or None.") # Start reading in the aberrations for the relevant SCAs. aberration_dict = {} @@ -280,15 +279,14 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): # Check for sane input PSF_dict. if len(PSF_dict) == 0 or len(PSF_dict) > galsim.wfirst.n_sca or \ min(PSF_dict.keys()) < 1 or max(PSF_dict.keys()) > galsim.wfirst.n_sca: - raise ValueError("PSF_dict must come from getPSF()!") + raise GalSimError("PSF_dict must come from getPSF()!") # Check if file already exists and warn about clobbering. - if os.path.exists(filename): - if clobber is False: - raise ValueError("Output file already exists, and clobber is not set!") + if os.path.isfile(filename): + if not clobber: + os.remove(filename) else: - import warnings - warnings.warn("Output file already exists, and will be clobbered.") + raise OSError("Output file %r already exists"%filename) # Check that bandpass list input is okay. It should be strictly a subset of the default list of # bandpasses. @@ -296,13 +294,13 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): bandpass_list = default_bandpass_list else: if not isinstance(bandpass_list[0], basestring): - raise ValueError("Expected input list of bandpass names!") + raise TypeError("Expected input list of bandpass names!") if not set(bandpass_list).issubset(default_bandpass_list): err_msg = '' for item in default_bandpass_list: err_msg += item+' ' - raise ValueError("Bandpass list must be a subset of the default list, containing %s" - %err_msg) + raise galsim.GalSimValueError("Invalid values in bandpass_list", bandpass_list, + default_bandpass_list) # Get all the WFIRST bandpasses. bandpass_dict = galsim.wfirst.getBandpasses() @@ -315,7 +313,7 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): PSF = PSF_dict[SCA] if not isinstance(PSF, galsim.ChromaticOpticalPSF) and \ not isinstance(PSF, galsim.InterpolatedChromaticObject): - raise galsim.GalSimError("Error, PSFs are not ChromaticOpticalPSFs.") + raise TypeError("PSFs are not ChromaticOpticalPSFs.") star = galsim.Gaussian(sigma=1.e-8, flux=1.) for bp_name in bandpass_list: @@ -414,7 +412,7 @@ def _read_aberrations(SCA): ChromaticOpticalPSF. """ if SCA < 1 or SCA > galsim.wfirst.n_sca: - raise ValueError("SCA requested is out of range: %d"%SCA) + raise galsim.GalSimRangeError("Invalid SCA requested", SCA, 1, galsim.wfirst.n_sca) # Construct filename. sca_str = '%02d'%SCA diff --git a/galsim/wfirst/wfirst_wcs.py b/galsim/wfirst/wfirst_wcs.py index 237c9f04a0a..601d2c2f023 100644 --- a/galsim/wfirst/wfirst_wcs.py +++ b/galsim/wfirst/wfirst_wcs.py @@ -324,8 +324,8 @@ def findSCA(wcs_dict, world_pos, include_border=False): """ # Sanity check args. if not isinstance(wcs_dict, dict): - raise ValueError("wcs_dict should be a dict containing WCS output by" - " galsim.wfirst.getWCS!"%galsim.wfirst.n_sca) + raise TypeError("wcs_dict should be a dict containing WCS output by " + "galsim.wfirst.getWCS!"%galsim.wfirst.n_sca) if not isinstance(world_pos, galsim.CelestialCoord): raise TypeError("Position on the sky must be given as a galsim.CelestialCoord!") @@ -447,8 +447,7 @@ def _parse_sip_file(file): for later calculations. """ if not os.path.exists(file): - raise galsim.GalSimError("Error, cannot find file that should have WFIRST SIP" - " coefficients: %s"%file) + raise OSError("Cannot find file that should have WFIRST SIP coefficients: %s"%file) # Parse the file, which comes from wfi_wcs_sip_gen_0.1.c provided by Jeff Kruk. data = np.loadtxt(file, usecols=[0, 3, 4, 5, 6, 7]).transpose() @@ -489,7 +488,7 @@ def _det_to_tangplane_positions(x_in, y_in): """ img_dist_coeff = np.array([-1.0873e-2, 3.5597e-03, 3.6515e-02, -1.8691e-4]) if not isinstance(x_in, galsim.Angle) or not isinstance(y_in, galsim.Angle): - raise ValueError("Input x_in and y_in are not galsim.Angles.") + raise TypeError("Input x_in and y_in are not galsim.Angles.") # The optical distortion model is defined in terms of separations in *degrees*. r_sq = (x_in/galsim.degrees)**2 + (y_in/galsim.degrees)**2 r = np.sqrt(r_sq) From 49795ef4fc21ae729652d8f2b2b39c8a3615e887 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 19:17:41 -0400 Subject: [PATCH 22/96] Check coverage on this branch (#755) --- .travis.yml | 1 + tests/.coveragerc | 11 ++--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index c95f91b1697..426d88c67a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ branches: only: - master - noboost + - '#755' language: python python: diff --git a/tests/.coveragerc b/tests/.coveragerc index cb662bed3f2..2f65ff1afbc 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -33,13 +33,7 @@ exclude_lines = # Don't complain if tests don't hit defensive checks of user input raise NotImplementedError - raise ValueError - raise RuntimeError raise TypeError - raise AttributeError - raise IndexError - raise IOError - raise KeyError # Don't complain about not hitting warning code if suppress_warnings is False: @@ -52,9 +46,8 @@ exclude_lines = if __name__ == .__main__.: # Don't complain about exceptional circumstances not under control of the test suite - except KeyboardInterrupt - except IOError - except OSError + except .*KeyboardInterrupt + except .*OSError # Or code for special cases of older versions of things. except ImportError From 4a375e2a6edb8b2acad7d01c8b2e92fc27b18003 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 19:40:39 -0400 Subject: [PATCH 23/96] Missed a GalSimWarning (#755) --- galsim/config/extra.py | 4 ++-- galsim/config/image_scattered.py | 4 ++-- galsim/config/input_powerspectrum.py | 2 +- galsim/config/output.py | 2 +- galsim/config/stamp.py | 2 +- galsim/deprecated/__init__.py | 4 ++-- galsim/des/des_meds.py | 4 ++-- galsim/main.py | 2 +- galsim/wfirst/wfirst_bandpass.py | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 81111473018..88f27c432df 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -223,8 +223,8 @@ def WriteExtraOutputs(config, main_data, logger=None): galsim.config.EnsureDir(file_name) if noclobber and os.path.isfile(file_name): # pragma: no cover - logger.warning('Not writing %s file %d = %s because output.noclobber = True' + - ' and file exists',key,config['file_num'],file_name) + logger.warning('Not writing %s file %d = %s because output.noclobber = True ' + 'and file exists',key,config['file_num'],file_name) continue if config['extra_last_file'].get(key, None) == file_name: diff --git a/galsim/config/image_scattered.py b/galsim/config/image_scattered.py index 5ce51f47e9a..1448933a22c 100644 --- a/galsim/config/image_scattered.py +++ b/galsim/config/image_scattered.py @@ -131,9 +131,9 @@ def buildImage(self, config, base, image_num, obj_num, logger): full_image[bounds] += stamps[k][bounds] else: logger.info( - "Object centered at (%d,%d) is entirely off the main image,\n"%( - stamps[k].center.x, stamps[k].center.y) + + "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)) diff --git a/galsim/config/input_powerspectrum.py b/galsim/config/input_powerspectrum.py index 4bbd6a1e611..526d72b1c17 100644 --- a/galsim/config/input_powerspectrum.py +++ b/galsim/config/input_powerspectrum.py @@ -156,7 +156,7 @@ def setupImage(self, input_obj, config, base, logger=None): # We don't care about the output here. This just builds the grid, which we'll # access for each object using its position. - logger.debug('image %d: PowerSpectrum buildGrid(grid_spacing=%s, ngrid=%s, center=%s, ' + + logger.debug('image %d: PowerSpectrum buildGrid(grid_spacing=%s, ngrid=%s, center=%s, ' 'interpolant=%s, variance=%s)', base.get('image_num',0), grid_spacing, ngrid, center, interpolant, variance) input_obj.buildGrid(grid_spacing=grid_spacing, ngrid=ngrid, center=center, diff --git a/galsim/config/output.py b/galsim/config/output.py index a3cf4369549..1c045476ff9 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -211,7 +211,7 @@ def BuildFile(config, file_num=0, image_num=0, obj_num=0, logger=None): if ('noclobber' in output and galsim.config.ParseValue(output, 'noclobber', config, bool)[0] and os.path.isfile(file_name)): - logger.warning('Skipping file %d = %s because output.noclobber = True' + + logger.warning('Skipping file %d = %s because output.noclobber = True' ' and file exists',file_num,file_name) return file_name, 0 diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 5985d201372..03e0bcb901a 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -515,7 +515,7 @@ def DrawBasic(prof, image, method, offset, config, base, logger, **kwargs): raise galsim.GalSimConfigError('n_photons is invalid with method != phot') if 'max_extra_noise' in config: logger.warning( - "Both 'max_extra_noise' and 'n_photons' are set in config dict, "+ + "Both 'max_extra_noise' and 'n_photons' are set in config dict, " "ignoring 'max_extra_noise'.") kwargs['n_photons'] = galsim.config.ParseValue(config, 'n_photons', base, int)[0] elif 'max_extra_noise' in config: diff --git a/galsim/deprecated/__init__.py b/galsim/deprecated/__init__.py index 8c62870a1a3..2e8ca2f7011 100644 --- a/galsim/deprecated/__init__.py +++ b/galsim/deprecated/__init__.py @@ -30,13 +30,13 @@ def depr(f, v, s1, s2=None): 2. You can add extra information if you want to point out something about the new syntax: depr('draw', 1.1, "drawImage(..., method='no_pixel')", - 'Note: drawImage has different args than draw did. ' + + 'Note: drawImage has different args than draw did. ' 'Read the docs for the method keywords carefully.') 3. If the deprecated function has no replacement, you can use '' for the first string. depr('calculateCovarianceMatrix', 1.3, '', - 'This functionality has been removed. If you have a need for it, please open '+ + 'This functionality has been removed. If you have a need for it, please open ' 'an issue requesting the functionality.') """ import warnings diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index 90edab33fff..81f54928ce9 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -286,8 +286,8 @@ def WriteMEDS(obj_list, file_name, clobber=True): # check if we are running out of memory if sys.getsizeof(vec) > MAX_MEMORY: # pragma: no cover raise galsim.GalSimError( - 'Running out of memory > %1.0fGB '%MAX_MEMORY/1.e9 + - '- you can increase the limit by changing galsim.des_meds.MAX_MEMORY') + "Running out of memory > %1.0fGB - you can increase the limit by changing " + "galsim.des_meds.MAX_MEMORY"%(%MAX_MEMORY/1.e9)) # update the start rows fields in the catalog cat['start_row'].append(start_rows) diff --git a/galsim/main.py b/galsim/main.py index ef814bb8a8a..c0a8aef5029 100644 --- a/galsim/main.py +++ b/galsim/main.py @@ -220,7 +220,7 @@ def main(): logging.basicConfig(format="%(message)s", level=logging_level, filename=args.log_file) logger = logging.getLogger('galsim') - logger.warn('Using config file %s', args.config_file, GalSimWarning) + logger.warning('Using config file %s', args.config_file) all_config = ReadConfig(args.config_file, args.file_type, logger) logger.debug('Successfully read in config file.') diff --git a/galsim/wfirst/wfirst_bandpass.py b/galsim/wfirst/wfirst_bandpass.py index bb5e239ef68..2ac2b5dbeff 100644 --- a/galsim/wfirst/wfirst_bandpass.py +++ b/galsim/wfirst/wfirst_bandpass.py @@ -107,7 +107,7 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, **kwargs): import warnings warnings.warn('default_thin_trunc is true, but other arguments have been passed' ' to getBandpasses(). Using the other arguments and ignoring' - ' default_thin_trunc.') + ' default_thin_trunc.', galsim.GalSimWarning) default_thin_trunc = False if len(kwargs) > 0: for key in kwargs: From 4a19e90ef37d4605867fb2f3b555a23c9f5629cd Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 23 Apr 2018 20:12:50 -0400 Subject: [PATCH 24/96] Allow for either order in dict -> str for GalSimIncompatibleValuesError str value (#755) --- galsim/des/des_meds.py | 2 +- tests/test_errors.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index 81f54928ce9..72d10ce68f1 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -287,7 +287,7 @@ def WriteMEDS(obj_list, file_name, clobber=True): if sys.getsizeof(vec) > MAX_MEMORY: # pragma: no cover raise galsim.GalSimError( "Running out of memory > %1.0fGB - you can increase the limit by changing " - "galsim.des_meds.MAX_MEMORY"%(%MAX_MEMORY/1.e9)) + "galsim.des_meds.MAX_MEMORY"%(MAX_MEMORY/1.e9)) # update the start rows fields in the catalog cat['start_row'].append(start_rows) diff --git a/tests/test_errors.py b/tests/test_errors.py index 18db93148f4..0a00fab727d 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -137,7 +137,10 @@ def test_galsim_incompatible_values_error(): err = galsim.GalSimIncompatibleValuesError("Test", a=1, b=2) print('str = ',str(err)) print('repr = ',repr(err)) - assert str(err) == "Test Values {'a': 1, 'b': 2}" + # This isn't completely deterministic across python versions. + str_possibilities = ["Test Values {'a': 1, 'b': 2}", + "Test Values {'b': 2, 'a': 1}"] + assert str(err) in str_possibilities assert err.values == dict(a=1, b=2) assert isinstance(err, galsim.GalSimError) assert isinstance(err, ValueError) From 228ea8a3c76bd36a6401fe973da1af6991ab5932 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 17:07:44 -0400 Subject: [PATCH 25/96] Some cleanup now that we are officially not supporting 2.6 (#755) --- SConstruct | 4 ++-- galsim/config/process.py | 31 +++---------------------------- galsim/config/stamp.py | 6 ++---- galsim/config/value.py | 3 --- galsim/download_cosmos.py | 7 +------ galsim/errors.py | 2 +- galsim/fitswcs.py | 3 --- galsim/pse.py | 4 ++-- galsim/scene.py | 3 +-- galsim/utilities.py | 31 ------------------------------- tests/test_random.py | 7 ------- 11 files changed, 12 insertions(+), 89 deletions(-) diff --git a/SConstruct b/SConstruct index f3945ab4644..e58a0d0daef 100644 --- a/SConstruct +++ b/SConstruct @@ -1328,8 +1328,8 @@ def GetPythonVersion(config): # there: if not result: py_version = '' - for v in ['2.7', '2,6', '3.4', '3.5', # supported versions first - '2.5', '2,4', '3.3', '3.2', '3.1', '3.0']: # these are mostly to give accurate logging and error messages + for v in ['2.7', '3.4', '3.5', '3.6', # supported versions first + '2.6', '2.5', '2,4', '3.3', '3.2', '3.1', '3.0']: # these are mostly to give accurate logging and error messages if v in py_inc or v in python: py_version = v break diff --git a/galsim/config/process.py b/galsim/config/process.py index 385b761e2f3..78d0e759d21 100644 --- a/galsim/config/process.py +++ b/galsim/config/process.py @@ -20,20 +20,7 @@ import galsim import logging import copy - - -# Python 2.6 doesn't include OrderedDict natively. There is a package ordereddict that you -# can pip install. But if the user hasn't done that, we'll just read into a regular dict. -# The only feature that requires the OrderedDict is the truth catalog output. With a regular -# dict the columns will appear in arbitrary order. -try: - from collections import OrderedDict -except ImportError: - try: - from ordereddict import OrderedDict - except ImportError: - OrderedDict = dict - +from collections import OrderedDict def MergeConfig(config1, config2, logger=None): """ @@ -123,20 +110,8 @@ def ReadJson(config_file): import json with open(config_file) as f: - try: - # cf. http://stackoverflow.com/questions/6921699/can-i-get-json-to-load-into-an-ordereddict-in-python - config = json.load(f, object_pairs_hook=OrderedDict) - except TypeError: # pragma: no cover - # for python2.6, json doesn't come with the object_pairs_hook, so - # try using simplejson, and if that doesn't work, just use a regular dict. - # Also, it seems that if the above line raises an exception, the file handle - # is not left at the beginning, so seek back to 0. - f.seek(0) - try: - import simplejson - config = simplejson.load(f, object_pairs_hook=OrderedDict) - except ImportError: - config = json.load(f) + # cf. http://stackoverflow.com/questions/6921699/can-i-get-json-to-load-into-an-ordereddict-in-python + config = json.load(f, object_pairs_hook=OrderedDict) # JSON files only ever define a single job, but we need to return a list with this one item. return [config] diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 03e0bcb901a..fc2c16260f4 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -116,7 +116,7 @@ def except_func(logger, proc, k, e, tr): # A list of keys that really belong in stamp, but are allowed in image both for convenience # and backwards-compatibility reasons. Any of these present will be copied over to # config['stamp'] if they exist in config['image']. -stamp_image_keys = ['offset', 'retry_failures', 'gsparams', 'draw_method', 'wmult', +stamp_image_keys = ['offset', 'retry_failures', 'gsparams', 'draw_method', 'n_photons', 'max_extra_noise', 'poisson_flux'] def SetupConfigObjNum(config, obj_num, logger=None): @@ -261,7 +261,7 @@ def SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos, logger=None # Ignore these when parsing the parameters for specific stamp types: stamp_ignore = ['xsize', 'ysize', 'size', 'image_pos', 'world_pos', 'offset', 'retry_failures', 'gsparams', 'draw_method', - 'wmult', 'n_photons', 'max_extra_noise', 'poisson_flux', + 'n_photons', 'max_extra_noise', 'poisson_flux', 'skip', 'reject', 'min_flux_frac', 'min_snr', 'max_snr'] valid_draw_methods = ('auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb') @@ -501,8 +501,6 @@ def DrawBasic(prof, image, method, offset, config, base, logger, **kwargs): kwargs['image'] = image kwargs['offset'] = offset kwargs['method'] = method - if 'wmult' in config and 'wmult' not in kwargs: # pragma: no cover - kwargs['wmult'] = galsim.config.ParseValue(config, 'wmult', base, float)[0] if 'wcs' not in kwargs and 'scale' not in kwargs: kwargs['wcs'] = base['wcs'].local(image_pos = base['image_pos']) if method == 'phot' and 'rng' not in kwargs: diff --git a/galsim/config/value.py b/galsim/config/value.py index d8ae824ce19..01e0f105adf 100644 --- a/galsim/config/value.py +++ b/galsim/config/value.py @@ -349,9 +349,6 @@ def GetAllParams(config, base, req={}, opt={}, single=[], ignore=[]): val, safe1 = ParseValue(config, key, base, value_type) safe = safe and safe1 kwargs[key] = val - # Just in case there are unicode strings. python 2.6 doesn't like them in kwargs. - if sys.version_info < (2,7): # pragma: no cover - kwargs = dict([(k.encode('utf-8'), v) for k,v in kwargs.items()]) return kwargs, safe diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 286f8e62f04..8dcacea8ea7 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -297,12 +297,7 @@ def download(url, target, unpack_dir, args, logger): def unpack(target, target_dir, unpack_dir, meta, args, logger): logger.info("Unpacking the tarball...") - #with tarfile.open(target) as tar: - # The above line works on python 2.7+. But to make sure we work for 2.6, we use the - # following workaround. - # cf. http://stackoverflow.com/questions/6086603/statement-with-and-tarfile - from contextlib import closing - with closing(tarfile.open(target)) as tar: + with tarfile.open(target) as tar: if args.verbosity >= 3: tar.list(verbose=True) elif args.verbosity >= 2: diff --git a/galsim/errors.py b/galsim/errors.py index fac5b37a439..0c1a0ac20b5 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -259,7 +259,7 @@ def __eq__(self, other): return repr(self) == repr(other) def __hash__(self): return hash(repr(self)) -# Note: By default python2.7 ignores DeprecationWarnings. Apparently they are really +# Note: By default python ignores DeprecationWarnings. Apparently they are really # for python system deprecations. GalSim deprecations are thus only subclassed from # GalSimWarning, not DeprecationWarning. class GalSimDeprecationWarning(GalSimWarning): diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 78d88150f33..c40144d8c7e 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -383,9 +383,6 @@ class PyAstWCS(CelestialWCS): https://pypi.python.org/pypi/starlink-pyast/ - Note: There were bugs in starlink.Ast prior to version 2.6, so if you have an earlier version, - you should upgrade to at least 2.6. - Initialization -------------- A PyAstWCS is initialized with one of the following commands: diff --git a/galsim/pse.py b/galsim/pse.py index 599433dd46c..240d88eb214 100644 --- a/galsim/pse.py +++ b/galsim/pse.py @@ -34,8 +34,8 @@ class PowerSpectrumEstimator(object): """ Class for estimating the shear power spectrum from gridded shears. - The PowerSpectrumEstimator class can be used even on systems where GalSim is not installed. It - just requires Python v2.6 or 2.7 and NumPy. + The PowerSpectrumEstimator class can be used even on systems where GalSim is not installed. + It just requires NumPy. This class stores all the data used in power spectrum estimation that is fixed with the geometry of the problem - the binning and spin weighting factors. diff --git a/galsim/scene.py b/galsim/scene.py index 02431bd69f0..6842290b915 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -853,8 +853,7 @@ def getParametricRecord(self, index): record = self.param_cat[self.orig_index[index]] # Convert to a dict, since on some systems, the numpy record doesn't seem to # pickle correctly. - #record_dict = { k:record[k] for k in record.dtype.names } # doesn't work in python 2.6 - record_dict = dict( (k,record[k]) for k in record.dtype.names ) # equivalent. + record_dict = { k:record[k] for k in record.dtype.names } return record_dict def canMakeReal(self): diff --git a/galsim/utilities.py b/galsim/utilities.py index cc200610e30..7e9144af6b3 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -573,37 +573,6 @@ def old_thin_tabulated_values(x, f, rel_err=1.e-4, preserve_range=False): # prag return newx, newf -def _gammafn(x): # pragma: no cover - """ - This code is not currently used, but in case we need a gamma function at some point, it will be - here in the utilities module. - - The gamma function is present in python2.7's math module, but not 2.6. So try using that, - and if it fails, use some code from RosettaCode: - http://rosettacode.org/wiki/Gamma_function#Python - """ - try: - import math - return math.gamma(x) - except AttributeError: - y = float(x) - 1.0 - sm = _gammafn._a[-1] - for an in _gammafn._a[-2::-1]: - sm = sm * y + an - return 1.0 / sm - -_gammafn._a = ( 1.00000000000000000000, 0.57721566490153286061, -0.65587807152025388108, - -0.04200263503409523553, 0.16653861138229148950, -0.04219773455554433675, - -0.00962197152787697356, 0.00721894324666309954, -0.00116516759185906511, - -0.00021524167411495097, 0.00012805028238811619, -0.00002013485478078824, - -0.00000125049348214267, 0.00000113302723198170, -0.00000020563384169776, - 0.00000000611609510448, 0.00000000500200764447, -0.00000000118127457049, - 0.00000000010434267117, 0.00000000000778226344, -0.00000000000369680562, - 0.00000000000051003703, -0.00000000000002058326, -0.00000000000000534812, - 0.00000000000000122678, -0.00000000000000011813, 0.00000000000000000119, - 0.00000000000000000141, -0.00000000000000000023, 0.00000000000000000002 - ) - def horner(x, coef, dtype=None): """Evaluate univariate polynomial using Horner's method. diff --git a/tests/test_random.py b/tests/test_random.py index dc207a27f63..38599497fa1 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1491,13 +1491,6 @@ def test_multiprocess(): """Test that the same random numbers are generated in single-process and multi-process modes. """ from multiprocessing import Process, Queue, current_process - # Workaround for a bug in python 2.6. The bug is that sys.stdin can be double closed if - # multiprocessing is used within something that already uses multiprocessing. - # Specifically, if we are using nosetests with multiple processes. - # See http://bugs.python.org/issue5313 for more info. - if sys.version_info < (2,7): - sys.stdin.close() - sys.stdin = open(os.devnull) def generate_list(seed): """Given a particular seed value, generate a list of random numbers. From 756ca84a5b5ed140b3d92be86680b51a9789c7a8 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 17:32:16 -0400 Subject: [PATCH 26/96] Improve test coverage of config module: values (#755) --- galsim/config/value.py | 48 +++----- galsim/config/value_eval.py | 4 +- tests/test_config_value.py | 216 +++++++++++++++++++++++++++++++++--- 3 files changed, 222 insertions(+), 46 deletions(-) diff --git a/galsim/config/value.py b/galsim/config/value.py index 01e0f105adf..1cc1a112bbc 100644 --- a/galsim/config/value.py +++ b/galsim/config/value.py @@ -290,12 +290,9 @@ def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): for (key, value_type) in req.items(): if key in config: get[key] = value_type - else: # pragma: no cover - if 'type' in config: - raise galsim.GalSimConfigError( - "Attribute %s is required for type = %s"%(key,config['type'])) - else: - raise galsim.GalSimConfigError("Attribute %s is required"%key) + 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(): @@ -309,21 +306,15 @@ def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): for (key, value_type) in s.items(): if key in config: count += 1 - if count > 1: # pragma: no cover - if 'type' in config: - raise galsim.GalSimConfigError( - "Only one of the attributes %s is allowed for type = %s"%( - s.keys(),config['type'])) - else: - raise galsim.GalSimConfigError( - "Only one of the attributes %s is allowed"%s.keys()) + 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: # pragma: no cover - if 'type' in config: - raise galsim.GalSimConfigError( - "One of the attributes %s is required for type = %s"%(s.keys(),config['type'])) - else: - raise galsim.GalSimConfigError("One of the attributes %s is required"%s.keys()) + 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 @@ -367,7 +358,7 @@ def _GetAngleValue(param): value = float(value) unit = galsim.AngleUnit.from_name(unit) return galsim.Angle(value, unit) - except (TypeError, AttributeError) as e: # pragma: no cover + except (ValueError, TypeError, AttributeError) as e: raise galsim.GalSimConfigError("Unable to parse %s as an Angle. Caught %s"%(param,e)) @@ -382,7 +373,7 @@ def _GetPositionValue(param): x, y = param.split(',') x = float(x.strip()) y = float(y.strip()) - except (TypeError, AttributeError) as e: # pragma: no cover + except (ValueError, TypeError, AttributeError) as e: raise galsim.GalSimConfigError( "Unable to parse %s as a PositionD. Caught %s"%(param,e)) return galsim.PositionD(x,y) @@ -400,16 +391,13 @@ def _GetBoolValue(param): try: val = bool(int(param)) return val - except (TypeError, AttributeError) as e: # pragma: no cover + except (ValueError, TypeError, AttributeError) as e: raise galsim.GalSimConfigError( "Unable to parse %s as a bool. Caught %s"%(param,e)) else: - try: - val = bool(param) - return val - except (TypeError, AttributeError) as e: # pragma: no cover - raise galsim.GalSimConfigError("Unable to parse %s as a bool. Caught %s"%(param,e)) - + # This always works. + # Everything in Python is convertible to bool. + return bool(param) # @@ -698,7 +686,7 @@ def _GenerateFromCurrent(config, base, value_type): try: return EvaluateCurrentValue(k, d, base, value_type) - except ValueError as e: # pragma: no cover + except (TypeError, ValueError) as e: raise galsim.GalSimConfigError("%s\nError generating Current value with key = %s"%(e,k)) diff --git a/galsim/config/value_eval.py b/galsim/config/value_eval.py index 7f7ba516ee9..cabe97fb249 100644 --- a/galsim/config/value_eval.py +++ b/galsim/config/value_eval.py @@ -153,7 +153,7 @@ def _GenerateFromEval(config, base, value_type): config['_fn'] = fn except KeyboardInterrupt: raise - except Exception as e: # pragma: no cover + except Exception as e: raise galsim.GalSimConfigError( "Unable to evaluate string %r as a %s\n%r"%(string, value_type, e)) @@ -177,7 +177,7 @@ def _GenerateFromEval(config, base, value_type): return val, safe except KeyboardInterrupt: raise - except Exception as e: # pragma: no cover + except Exception as e: raise galsim.GalSimConfigError( "Unable to evaluate string %r as a %s\n%r"%(config['str'],value_type, e)) diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 8b0cd2c7b01..8126703e376 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -64,6 +64,7 @@ def test_float_value(): 'gauss4' : { 'type' : 'RandomGaussian', 'sigma' : 0.5, 'min' : 0, 'max' : 0.8 }, 'gauss5' : { 'type' : 'RandomGaussian', 'sigma' : 0.3, 'mean' : 0.5, 'min' : 0, 'max' : 0.5 }, + 'gauss6' : { 'type' : 'RandomGaussian', 'sigma' : 1 }, 'dist1' : { 'type' : 'RandomDistribution', 'function' : 'config_input/distribution.txt', 'interpolant' : 'linear' }, 'dist2' : { 'type' : 'RandomDistribution', @@ -95,9 +96,22 @@ def test_float_value(): 'sum1' : { 'type' : 'Sum', 'items' : [ 72, '2.33', { 'type' : 'Dict', 'key' : 'f' } ] }, 'nfw' : { 'type' : 'NFWHaloMagnification' }, 'ps' : { 'type' : 'PowerSpectrumMagnification' }, - 'no_type' : { 'value' : 34. }, - 'bad_key' : { 'type' : 'RandomGaussian', 'sig' : 1 }, - 'bad_value' : { 'type' : 'RandomGaussian', 'sigma' : 'not a number' }, + 'bad1' : { 'value' : 34. }, + 'bad2' : { 'type' : 'RandomGaussian', 'sig' : 1 }, + 'bad3' : { 'type' : 'RandomGaussian', 'sigma' : 'not a number' }, + 'bad4' : { 'type' : 'Invalid', 'sig' : 1 }, + 'bad5' : { 'type' : 'Sequence', 'first' : 1, 'last' : 2.1, 'repeat' : -2 }, + 'bad6' : { 'type' : 'Sequence', 'first' : 1, 'last' : 2.1, 'nitems' : 12 }, + 'bad7' : { 'type' : 'RandomDistribution', + 'x' : [ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 ], + 'interpolant' : 'linear' }, + 'bad8' : { 'type' : 'RandomDistribution', 'function' : 'x*x', + 'x' : [ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 ], + 'f' : [ 0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.1, 0.1, 0.1, 0.1 ], + 'interpolant' : 'linear' }, + 'bad9' : { 'type' : 'RandomDistribution', 'interpolant' : 'linear' }, + 'bad10' : { 'type' : 'RandomDistribution', 'function' : 'x*x', 'x_log' : True }, + 'bad11' : { 'type' : 'RandomDistribution', 'function' : 'x*x', 'f_log' : True }, # Some items that would normally be set by the config processing 'image_xsize' : 2000, @@ -315,11 +329,11 @@ def test_float_value(): # Test NFWHaloMagnification galsim.config.SetupInputsForImage(config, None) # Raise an error because no world_pos - with assert_raises(ValueError): + with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config,'nfw',config, float) config['world_pos'] = galsim.PositionD(6,8) # Still raise an error because no redshift - with assert_raises(ValueError): + with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config,'nfw',config, float) # With this, it should work. config['gal'] = { 'redshift' : gal_z } @@ -359,6 +373,13 @@ def test_float_value(): assert "Warning: NFWHalo mu = -163.631846 means strong lensing." in cl.output np.testing.assert_almost_equal(nfw4, 3000.) + # Negative max_mu is invalid. + galsim.config.RemoveCurrent(config) + config['nfw']['max_mu'] = -3. + del config['nfw']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'nfw',config, float) + # Test PowerSpectrumMagnification ps = galsim.PowerSpectrum(e_power_function='np.exp(-k**0.2)') galsim.config.RemoveCurrent(config) @@ -391,6 +412,21 @@ def test_float_value(): assert "Warning: PowerSpectrum mu = 29.287659 means strong lensing." in cl.output np.testing.assert_almost_equal(ps2b, 25.) + # Or set a different maximum + galsim.config.RemoveCurrent(config) + config['ps']['max_mu'] = 30. + del config['ps']['_get'] + ps3 = galsim.config.ParseValue(config,'ps',config, float)[0] + np.testing.assert_almost_equal(ps3, 29.28765853271335) + + # Negative max_mu is invalid. + galsim.config.RemoveCurrent(config) + config['ps']['max_mu'] = -3. + del config['ps']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'ps',config, float) + config['ps']['max_mu'] = 25. + # Out of bounds results in shear = 0, and a warning. galsim.config.RemoveCurrent(config) config['world_pos'] = galsim.PositionD(1000,2000) @@ -401,13 +437,43 @@ def test_float_value(): assert "Warning: position (1000.000000,2000.000000) not within the bounds" in cl.output np.testing.assert_almost_equal(ps2c, 1.) + # Error if no world_pos + del config['world_pos'] + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'ps', config, float) + # Should raise a GalSimConfigError if there is no type in the dict with assert_raises(galsim.GalSimConfigError): - galsim.config.ParseValue(config, 'no_type', config, float) + galsim.config.ParseValue(config, 'bad1', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad2', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad3', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad4', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad5', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad6', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad7', config, float) with assert_raises(galsim.GalSimConfigError): - galsim.config.ParseValue(config, 'bad_key', config, float) + galsim.config.ParseValue(config, 'bad8', config, float) with assert_raises(galsim.GalSimConfigError): - galsim.config.ParseValue(config, 'bad_value', config, float) + galsim.config.ParseValue(config, 'bad9', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad10', config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'bad11', config, float) + + # Error if given the wrong type. Should be float, not np.float16. + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'gauss6',config, np.float16) + # Different path to (different) error if already processed into a _gen_fn. + galsim.config.ParseValue(config,'gauss1',config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'gauss1',config, np.float16) @timer @@ -456,6 +522,11 @@ def test_int_value(): 'dict2' : { 'type' : 'Dict', 'num' : 1, 'key' : 'i' }, 'dict3' : { 'type' : 'Dict', 'num' : 2, 'key' : 'i' }, 'sum1' : { 'type' : 'Sum', 'items' : [ 72.3, '2', { 'type' : 'Dict', 'key' : 'i' } ] }, + 'cur1' : { 'type' : 'Current', 'key' : 'val1' }, + 'cur2' : { 'type' : 'Current', 'key' : 'list2.index.step' }, + 'bad1' : 'left', + 'bad2' : int, + 'bad3' : { 'type' : 'Current', 'key' : 'list2.index.type' } } test_yaml = True @@ -621,6 +692,18 @@ def test_int_value(): sum1 = galsim.config.ParseValue(config,'sum1', config, int)[0] np.testing.assert_almost_equal(sum1, 72 + 2 + 17) + cur1 = galsim.config.ParseValue(config,'cur1', config, int)[0] + np.testing.assert_array_equal(cur1, 9) + cur2 = galsim.config.ParseValue(config,'cur2', config, int)[0] + np.testing.assert_array_equal(cur2, -3) + + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1', config, int) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2', config, int) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, int) + @timer def test_bool_value(): @@ -659,7 +742,10 @@ def test_bool_value(): 'index' : { 'type' : 'Sequence', 'first' : 10, 'step' : -3 } }, 'dict1' : { 'type' : 'Dict', 'key' : 'b' }, 'dict2' : { 'type' : 'Dict', 'num' : 1, 'key' : 'b' }, - 'dict3' : { 'type' : 'Dict', 'num' : 2, 'key' : 'b' } + 'dict3' : { 'type' : 'Dict', 'num' : 2, 'key' : 'b' }, + 'bad1' : 'left', + 'bad2' : 'nope', + 'bad3' : { 'type' : 'RandomBinomial', 'N' : 2 }, } test_yaml = True @@ -774,6 +860,14 @@ def test_bool_value(): dict.append(False) np.testing.assert_array_equal(dict, [ True, False, False ]) + # Test bad values + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1',config, bool) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2',config, bool) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, bool) + @timer def test_str_value(): @@ -812,7 +906,12 @@ def test_str_value(): 'Goodbye cruel world.', ', said Pink.'] }, 'dict1' : { 'type' : 'Dict', 'key' : 's' }, 'dict2' : { 'type' : 'Dict', 'num' : 1, 'key' : 's' }, - 'dict3' : { 'type' : 'Dict', 'num' : 2, 'key' : 's' } + 'dict3' : { 'type' : 'Dict', 'num' : 2, 'key' : 's' }, + 'bad1' : { 'type' : 'FormattedStr', 'format' : 'realgal%02q.fits', 'items' : [4,5,6] }, + 'bad2' : { 'type' : 'FormattedStr', 'format' : 'realgal%02', 'items' : [4,5,6] }, + 'bad3' : { 'type' : 'FormattedStr', 'format' : 'realgal%02d_%d.fits', 'items' : [4,5,6] }, + 'bad4' : { 'type' : 'List', 'items' : 'Beautiful plumage! Ay?' }, + 'bad5' : { 'type' : 'List', 'items' : [ 'Beautiful', 'plumage!', 'Ay?' ], 'index' : 5 }, } test_yaml = True @@ -898,6 +997,16 @@ def test_str_value(): dict.append('Brian') np.testing.assert_array_equal(dict, [ 'Life', 'of', 'Brian' ]) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1',config, str) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2',config, str) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, str) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad4',config, str) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad5',config, str) @timer def test_angle_value(): @@ -933,7 +1042,9 @@ def test_angle_value(): 'items' : [ 73 * galsim.arcmin, 8.9 * galsim.arcmin, 3.14 * galsim.arcmin ] }, - 'sum1' : { 'type' : 'Sum', 'items' : [ 72 * galsim.degrees, '2.33 degrees' ] } + 'sum1' : { 'type' : 'Sum', 'items' : [ 72 * galsim.degrees, '2.33 degrees' ] }, + 'bad1' : '1.9 * galsim.rradds', + 'bad2' : { 'type' : 'Sum', 'items' : 72 * galsim.degrees }, } galsim.config.ProcessInput(config) @@ -1027,6 +1138,11 @@ def test_angle_value(): sum1 = galsim.config.ParseValue(config,'sum1', config, galsim.Angle)[0] np.testing.assert_almost_equal(sum1 / galsim.degrees, 72 + 2.33) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1', config, galsim.Angle) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2', config, galsim.Angle) + @timer def test_shear_value(): @@ -1056,12 +1172,15 @@ def test_shear_value(): galsim.Shear(g1 = 0.1, g2 = 0.0) ] }, 'nfw' : { 'type' : 'NFWHaloShear' }, 'ps' : { 'type' : 'PowerSpectrumShear' }, + 'bad1' : { 'type' : 'G1G2', 'g1' : 0.5 }, + 'bad2' : { 'type' : 'G1G2' }, + 'bad3' : { 'type' : 'G1G2', 'g1' : 0.5, 'g2' : -0.1, 'g3' : 0.3 }, 'input' : { 'nfw_halo' : { 'mass' : halo_mass, 'conc' : halo_conc, 'redshift' : halo_z }, 'power_spectrum' : { 'e_power_function' : 'np.exp(-k**0.2)', 'grid_spacing' : 10, 'interpolant' : 'linear', 'ngrid' : 40, 'center' : '5,5' }, - } + }, } # Test direct values @@ -1137,11 +1256,11 @@ def test_shear_value(): galsim.config.ProcessInput(config) galsim.config.SetupInputsForImage(config, None) # Raise an error because no world_pos - with assert_raises(ValueError): + with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config, 'nfw', config, galsim.Shear) config['world_pos'] = galsim.PositionD(6,8) # Still raise an error because no redshift - with assert_raises(ValueError): + with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config, 'nfw', config, galsim.Shear) # With this, it should work. config['gal'] = { 'redshift' : gal_z } @@ -1207,6 +1326,19 @@ def test_shear_value(): assert "Warning: position (1000.000000,2000.000000) not within the bounds" in cl.output np.testing.assert_almost_equal((ps2c.g1, ps2c.g2), (0,0)) + # Error if no world_pos + del config['world_pos'] + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config, 'ps', config, galsim.Shear) + + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1',config, galsim.Shear) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2',config, galsim.Shear) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, galsim.Shear) + @timer def test_pos_value(): @@ -1214,6 +1346,7 @@ def test_pos_value(): """ config = { 'val1' : galsim.PositionD(0.1,0.2), + 'val2' : '0.1, 0.2', 'xy1' : { 'type' : 'XY', 'x' : 1.3, 'y' : 2.4 }, 'ran1' : { 'type' : 'RandomCircle', 'radius' : 3 }, 'ran2' : { 'type' : 'RandomCircle', 'radius' : 1, 'center' : galsim.PositionD(3,7) }, @@ -1228,13 +1361,29 @@ def test_pos_value(): galsim.PositionD(-0.5, 0.2), galsim.PositionD(0.1, 0.0) ] }, 'radec' : { 'type' : 'RADec', 'ra' : 13.4 * galsim.hours, 'dec' : -0.3 * galsim.degrees }, + 'bad1' : '0.1, 0.2, 0.3', + 'bad2' : '0.1,', + 'bad3' : '0.1', + 'bad4' : 'red, blue', } + # Also use this to check CopyConfig and CleanConfig. Processing adds a lot to the + # config dict for efficiency. But CopyConfig should copy the current state, and + # CleanConfig should get it back to a clean state after processing is done. + # The one catch is that it needs to know what the top-level fields are, and we use non-standard + # ones here. So add them to top_level_fields. + galsim.config.top_level_fields += config.keys() + orig_config = galsim.config.CopyConfig(config) + assert orig_config == config # Test direct values val1 = galsim.config.ParseValue(config,'val1',config, galsim.PositionD)[0] np.testing.assert_almost_equal(val1.x, 0.1) np.testing.assert_almost_equal(val1.y, 0.2) + val2 = galsim.config.ParseValue(config,'val2',config, galsim.PositionD)[0] + np.testing.assert_almost_equal(val1.x, 0.1) + np.testing.assert_almost_equal(val1.y, 0.2) + xy1 = galsim.config.ParseValue(config,'xy1',config, galsim.PositionD)[0] np.testing.assert_almost_equal(xy1.x, 1.3) np.testing.assert_almost_equal(xy1.y, 2.4) @@ -1299,6 +1448,29 @@ def test_pos_value(): np.testing.assert_almost_equal(radec.ra / galsim.hours, 13.4) np.testing.assert_almost_equal(radec.dec / galsim.degrees, -0.3) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1',config, galsim.PositionD) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2',config, galsim.PositionD) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, galsim.PositionD) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad4',config, galsim.PositionD) + + clean_config = galsim.config.CleanConfig(config) + # Remove all current values + galsim.config.RemoveCurrent(clean_config) + # And a few extra things we added by hand. + for key in ['obj_num', 'index_key', 'rng', 'image_num']: + del clean_config[key] + # And one extra thing that gets set as a default, but CleanConfig doesn't remove + del clean_config['list1']['index'] + # Finally, this value got changed to a real Position, so it won't match the original + # unless we manually set it back to a string. + clean_config['val2'] = '0.1, 0.2' + print('orig_config = ',orig_config) + print('cleaned config = ',clean_config) + assert clean_config == orig_config @timer def test_eval(): @@ -1340,6 +1512,11 @@ def test_eval(): # A couple more to cover the other various letter prefixes. 'eval18' : { 'type' : 'Eval', 'str' : 'np.exp(-eval(half) * theta.rad**lit_two)' }, 'eval19' : { 'type' : 'Eval', 'str' : 'np.exp(-shear.g1 * pos.x * coord.ra.rad)' }, + 'bad1' : { 'type' : 'Eval', 'str' : 'npexp(-0.5)' }, + 'bad2' : { 'type' : 'Eval', 'str' : 'np.exp(-0.5 * x**2)', 'x' : 1.8 }, + 'bad3' : { 'type' : 'Eval', 'str' : 'np.exp(-0.5 * x**2)', 'qx' : 1.8 }, + 'bad4' : { 'type' : 'Eval', 'str' : 'np.exp(-0.5 * q**2)', 'fx' : 1.8 }, + 'bad5' : { 'type' : 'Eval', 'eval_str' : 'np.exp(-0.5 * x**2)', 'fx' : 1.8 }, # These would be set by config in real runs, but just add them here for the tests. 'image_pos' : galsim.PositionD(1.8,13), @@ -1360,6 +1537,17 @@ def test_eval(): print('i = ',i, 'val = ',test_val,true_val) np.testing.assert_almost_equal(test_val, true_val) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad1',config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad2',config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad3',config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad4',config, float) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad5',config, float) + # Test the evaluation in RandomDistribution # Example config taken directly from Issue #776: config['shear'] = { From 23ce8ef6bac14625fc5ac947a07951ac3c45deeb Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 17:42:04 -0400 Subject: [PATCH 27/96] Improve test coverage of config module: gsobject (#755) --- galsim/config/gsobject.py | 2 +- galsim/config/input_real.py | 2 +- tests/test_config_gsobject.py | 244 ++++++++++++++++++++++++++++++++-- 3 files changed, 232 insertions(+), 16 deletions(-) diff --git a/galsim/config/gsobject.py b/galsim/config/gsobject.py index f8137f31d86..a0f82ca11b2 100644 --- a/galsim/config/gsobject.py +++ b/galsim/config/gsobject.py @@ -173,7 +173,7 @@ def BuildGSObject(config, key, base=None, gsparams={}, logger=None): if key == 'psf': try: param['saved_re'] = gsobject.half_light_radius - except AttributeError: + except (AttributeError, NotImplementedError, TypeError): pass # Apply any dilation, ellip, shear, etc. modifications. diff --git a/galsim/config/input_real.py b/galsim/config/input_real.py index 5fc5efbb39f..542a85f476d 100644 --- a/galsim/config/input_real.py +++ b/galsim/config/input_real.py @@ -54,7 +54,7 @@ def _BuildRealGalaxy(config, base, ignore, gsparams, logger, param_name='RealGal if 'index' in kwargs: index = kwargs['index'] - if index >= real_cat.getNObjects(): + if index >= real_cat.getNObjects() or index < 0: raise galsim.GalSimConfigError( "index=%s has gone past the number of entries in the RealGalaxyCatalog"%index) diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index ded208fd3ac..d79822f2416 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -47,6 +47,14 @@ def test_gaussian(): 'shear' : galsim.Shear(g1=-0.15, g2=0.2) }, 'gal6' : { 'type' : 'DeltaFunction' , 'flux' : 72.5 }, + 'bad1' : { 'type' : 'Gaussian' , 'fwhm' : 2, 'sigma' : 3, 'flux' : 100 }, + 'bad2' : { 'type' : 'Gaussian' }, + 'bad3' : { 'type' : 'Gaussian', 'sig' : 4 }, + 'bad4' : { 'sigma' : 2 }, + 'bad5' : { 'type' : 'Gauss', 'sigma' : 2 }, + 'bad6' : { 'type' : 'Gaussian', 'resolution' : 1.5 }, # requires psf field. + 'bad7' : { 'type' : 'Gaussian', 'sigma' : 2, 'resolution' : 1.5 }, # can't give sigma + 'bad8' : { 'type' : 'Gaussian', 'half_light_radius' : 2, 'resolution' : 1.5 }, # or hlr } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -80,6 +88,41 @@ def test_gaussian(): gal6c = galsim.Gaussian(sigma = 1.e-10, flux = 72.5) gsobject_compare(gal6a, gal6c, conv=galsim.Gaussian(sigma=0.01)) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad4') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad5') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad6') + + # Test various invalid ways to use resolution. + # This psf cannot be used for resolution, since no half_light_radius field. + psf_file = os.path.join('SBProfile_comparison_images','gauss_smallshear.fits') + config['psf'] = { 'type' : 'InterpolatedImage', 'image' : psf_file } + psf = galsim.config.BuildGSObject(config, 'psf') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad6') + # This has half_light_radius, but it raises an exception for obscuration != 1 + config['psf'] = { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'obscuration' : 0.3 } + psf = galsim.config.BuildGSObject(config, 'psf') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad6') + # This finally works. + config['psf'] = { 'type' : 'Airy' , 'lam_over_diam' : 0.4 } + psf = galsim.config.BuildGSObject(config, 'psf') + gal = galsim.config.BuildGSObject(config, 'bad6') + # Can't give a different size along with resolution. + with assert_raises(galsim.GalSimConfigError): + gal = galsim.config.BuildGSObject(config, 'bad7') + with assert_raises(galsim.GalSimConfigError): + gal = galsim.config.BuildGSObject(config, 'bad8') + @timer def test_moffat(): @@ -101,6 +144,9 @@ def test_moffat(): 'shear' : galsim.Shear(g1=-0.15, g2=0.2), 'gsparams' : { 'maxk_threshold' : 1.e-2 } }, + 'bad1' : { 'type' : 'Moffat' , 'beta' : 1.4, 'scale_radius' : 2, 'fwhm' : 3 }, + 'bad2' : { 'type' : 'Moffat' , 'beta' : 1.4 }, + 'bad3' : { 'type' : 'Moffat' , 'beth' : 1.4, 'fwhm' : 8 }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -137,6 +183,13 @@ def test_moffat(): with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c, conv=galsim.Gaussian(sigma=0.01)) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + @timer def test_airy(): @@ -157,7 +210,10 @@ def test_airy(): 'gal5' : { 'type' : 'Airy' , 'lam_over_diam' : 45, 'gsparams' : { 'xvalue_accuracy' : 1.e-2 } }, - 'gal6' : { 'type' : 'Airy' , 'lam' : 400., 'diam' : 4.0, 'scale_unit' : 'arcmin' } + 'gal6' : { 'type' : 'Airy' , 'lam' : 400., 'diam' : 4.0, 'scale_unit' : 'arcmin' }, + 'bad1' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'lam' : 400, 'diam' : 10 }, + 'bad2' : { 'type' : 'Airy' , 'flux' : 1.3 }, + 'bad3' : { 'type' : 'Airy' , 'lam_over_diam' : 0.4, 'obsc' : 0.3, 'flux' : 100 }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -196,6 +252,13 @@ def test_airy(): with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + @timer def test_kolmogorov(): @@ -207,7 +270,7 @@ def test_kolmogorov(): 'gal3' : { 'type' : 'Kolmogorov' , 'half_light_radius' : 2, 'flux' : 1.e6, 'ellip' : { 'type' : 'QBeta' , 'q' : 0.6, 'beta' : 0.39 * galsim.radians } }, - 'gal4' : { 'type' : 'Kolmogorov' , 'lam_over_r0' : 1, 'flux' : 50, + 'gal4' : { 'type' : 'Kolmogorov' , 'lam' : 400, 'r0_500' : 0.15, 'flux' : 50, 'dilate' : 3, 'ellip' : galsim.Shear(e1=0.3), 'rotate' : 12 * galsim.degrees, 'magnify' : 1.03, 'shear' : galsim.Shear(g1=0.03, g2=-0.05), @@ -215,7 +278,10 @@ def test_kolmogorov(): }, 'gal5' : { 'type' : 'Kolmogorov' , 'lam_over_r0' : 1, 'flux' : 50, 'gsparams' : { 'integration_relerr' : 1.e-2, 'integration_abserr' : 1.e-4 } - } + }, + 'bad1' : { 'type' : 'Kolmogorov' , 'fwhm' : 2, 'lam_over_r0' : 3, 'flux' : 100 }, + 'bad2' : { 'type' : 'Kolmogorov', 'flux' : 100 }, + 'bad3' : { 'type' : 'Kolmogorov' , 'lam_over_r0' : 2, 'lam' : 400, 'r0' : 0.15 }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -232,7 +298,7 @@ def test_kolmogorov(): gsobject_compare(gal3a, gal3b) gal4a = galsim.config.BuildGSObject(config, 'gal4')[0] - gal4b = galsim.Kolmogorov(lam_over_r0 = 1, flux = 50) + gal4b = galsim.Kolmogorov(lam=400, r0_500=0.15, flux = 50) gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees) gal4b = gal4b.lens(0.03, -0.05, 1.03).shift(dx = 0.7, dy = -1.2) gsobject_compare(gal4a, gal4b) @@ -247,6 +313,12 @@ def test_kolmogorov(): with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') @timer def test_opticalpsf(): @@ -279,7 +351,13 @@ def test_opticalpsf(): os.path.join(".","Optics_comparison_images","sample_pupil_rolled.fits"), 'pupil_angle' : 27.*galsim.degrees }, 'gal6' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'flux' : 70., - 'obscuration' : 0.1 } + 'aberrations' : [0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13], + 'obscuration' : 0.1 }, + 'gal7' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'aberrations' : []}, + 'bad1' : {'type' : 'OpticalPSF' , 'lam' : 874.0, 'diam' : 7.4, 'lam_over_diam' : 0.2}, + 'bad2' : {'type' : 'OpticalPSF' , 'lam_over_diam' : 0.2, + 'aberrations' : "0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13"}, + 'bad3' : {'type' : 'OpticalPSF' , 'lam_over_diam' : 0.2, 'aberr' : []}, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -317,9 +395,23 @@ def test_opticalpsf(): gsobject_compare(gal5a, gal5b) gal6a = galsim.config.BuildGSObject(config, 'gal6')[0] - gal6b = galsim.OpticalPSF(lam=874., diam=7.4, flux=70., obscuration=0.1) + aberrations = np.zeros(12, dtype=float) + aberrations[4:] = [0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13] + gal6b = galsim.OpticalPSF(lam=874., diam=7.4, flux=70., obscuration=0.1, + aberrations=aberrations) gsobject_compare(gal6a, gal6b) + gal7a = galsim.config.BuildGSObject(config, 'gal7')[0] + gal7b = galsim.OpticalPSF(lam=874., diam=7.4) + gsobject_compare(gal7a, gal7b) + + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + @timer def test_exponential(): @@ -339,7 +431,9 @@ def test_exponential(): }, 'gal5' : { 'type' : 'Exponential' , 'scale_radius' : 1, 'flux' : 50, 'gsparams' : { 'kvalue_accuracy' : 1.e-2 } - } + }, + 'bad1' : { 'type' : 'Exponential' , 'scale_radius' : 2, 'half_light_radius' : 3 }, + 'bad2' : { 'type' : 'Exponential' }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -371,6 +465,10 @@ def test_exponential(): with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c, conv=galsim.Gaussian(sigma=1)) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') @timer def test_sersic(): @@ -397,7 +495,11 @@ def test_sersic(): 'gal7' : { 'type' : 'Sersic' , 'n' : 3.2, 'half_light_radius' : 1.7, 'flux' : 50, 'trunc' : 4.3, 'gsparams' : { 'realspace_relerr' : 1.e-2 , 'realspace_abserr' : 1.e-4 } - } + }, + 'bad1' : { 'type' : 'Sersic' , 'n' : 0.1, 'half_light_radius' : 3.5 }, + 'bad2' : { 'type' : 'Sersic' , 'n' : 11.1, 'half_light_radius' : 3.5 }, + 'bad3' : { 'type' : 'Sersic' , 'n' : 1.1 }, + 'bad4' : { 'type' : 'Sersic' , 'n' : 1.1, 'half_light_radius' : 3.5, 'scale_radius' : 2 }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -452,6 +554,15 @@ def test_sersic(): with assert_raises(AssertionError): gsobject_compare(gal7a, gal7c, conv=conv) + with assert_raises(galsim.GalSimRangeError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimRangeError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad4') + @timer def test_devaucouleurs(): @@ -527,6 +638,12 @@ def test_inclined_exponential(): 'half_light_radius' : 1, 'flux' : 50, 'gsparams' : { 'minimum_fft_size' : 256 } }, + 'bad1' : { 'type' : 'InclinedExponential' , 'inclination' : 0.7 * galsim.radians, + 'half_light_radius' : 1, 'scale_radius' : 2 }, + 'bad2' : { 'type' : 'InclinedExponential' , 'inclination' : 0.7 * galsim.radians, + 'scale_h_over_r' : 0.2 }, + 'bad3' : { 'type' : 'InclinedExponential' , 'inclination' : 0.7 * galsim.radians, + 'scale_radius' : 1, 'scale_h_over_r' : 0.2, 'scale_height' : 0.1 }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -552,14 +669,22 @@ def test_inclined_exponential(): gal5a = galsim.config.BuildGSObject(config, 'gal5')[0] gsparams = galsim.GSParams(minimum_fft_size=256) - gal5b = galsim.InclinedExponential(inclination=0.7 * galsim.radians, half_light_radius=1, flux=50, gsparams=gsparams) + gal5b = galsim.InclinedExponential(inclination=0.7 * galsim.radians, half_light_radius=1, + flux=50, gsparams=gsparams) gsobject_compare(gal5a, gal5b, conv=galsim.Gaussian(sigma=1)) # Make sure they don't match when using the default GSParams - gal5c = galsim.InclinedExponential(inclination=0.7 * galsim.radians, half_light_radius=1, flux=50) + gal5c = galsim.InclinedExponential(inclination=0.7 * galsim.radians, half_light_radius=1, + flux=50) with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c, conv=galsim.Gaussian(sigma=1)) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimError): + galsim.config.BuildGSObject(config, 'bad3') @timer def test_inclined_sersic(): @@ -592,7 +717,8 @@ def test_inclined_sersic(): gsobject_compare(gal1a, gal1b) gal2a = galsim.config.BuildGSObject(config, 'gal2')[0] - gal2b = galsim.InclinedSersic(n=3.5, inclination=21 * galsim.degrees, scale_radius=0.007, flux=100) + gal2b = galsim.InclinedSersic(n=3.5, inclination=21 * galsim.degrees, scale_radius=0.007, + flux=100) gsobject_compare(gal2a, gal2b) gal3a = galsim.config.BuildGSObject(config, 'gal3')[0] @@ -610,11 +736,13 @@ def test_inclined_sersic(): gal5a = galsim.config.BuildGSObject(config, 'gal5')[0] gsparams = galsim.GSParams(minimum_fft_size=256) - gal5b = galsim.InclinedSersic(n=0.7, inclination=0.7 * galsim.radians, half_light_radius=1, flux=50, gsparams=gsparams) + gal5b = galsim.InclinedSersic(n=0.7, inclination=0.7 * galsim.radians, half_light_radius=1, + flux=50, gsparams=gsparams) gsobject_compare(gal5a, gal5b, conv=galsim.Gaussian(sigma=1)) # Make sure they don't match when using the default GSParams - gal5c = galsim.InclinedSersic(n=0.7, inclination=0.7 * galsim.radians, half_light_radius=1, flux=50) + gal5c = galsim.InclinedSersic(n=0.7, inclination=0.7 * galsim.radians, half_light_radius=1, + flux=50) with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c, conv=galsim.Gaussian(sigma=1)) @@ -695,7 +823,9 @@ def test_realgalaxy(): 'gal7' : { 'type' : 'RealGalaxy' , 'random' : True}, # I admit the one below is odd (why would you specify "random" and have it be False?) but # one could imagine setting it based on some probabilistic process... - 'gal8' : { 'type' : 'RealGalaxy' , 'random' : False} + 'gal8' : { 'type' : 'RealGalaxy' , 'random' : False}, + 'bad1' : { 'type' : 'RealGalaxy' , 'index' : -3 }, + 'bad2' : { 'type' : 'RealGalaxy' , 'index' : 3000 }, } rng = galsim.UniformDeviate(1234) config['rng'] = galsim.UniformDeviate(1234) # A second copy starting with the same seed. @@ -769,6 +899,12 @@ def test_realgalaxy(): gal8b = galsim.RealGalaxy(real_cat, index=7) gsobject_compare(gal8a, gal8b, conv=conv) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + + @timer def test_cosmosgalaxy(): """Test various ways to build a COSMOSGalaxy @@ -1037,6 +1173,21 @@ def test_add(): ], 'flux' : 170. }, + 'bad1' : { + 'type' : 'Add' , + 'items' : { 'type' : 'Gaussian', 'sigma' : 2, 'flux' : 0.3 }, + }, + 'bad2' : { + 'type' : 'Add' , + 'items' : [], + }, + 'bad3' : { + 'type' : 'Add', + 'items' : 'invalid', + }, + 'bad4' : { + 'type' : 'Add', + }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -1110,6 +1261,15 @@ def test_add(): assert ("Warning: Automatic flux for the last item in Sum (to make the total flux=1) " + "resulted in negative flux = -0.200000 for that item") in cl.output + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad4') + @timer def test_convolve(): @@ -1173,6 +1333,21 @@ def test_convolve(): { 'type' : 'Gaussian' , 'sigma' : 2 }, ] }, + 'bad1' : { + 'type' : 'Convolve' , + 'items' : { 'type' : 'Gaussian', 'sigma' : 2, 'flux' : 0.3 }, + }, + 'bad2' : { + 'type' : 'Convolve' , + 'items' : [], + }, + 'bad3' : { + 'type' : 'Convolve' , + 'items' : 'invalid', + }, + 'bad4' : { + 'type' : 'Convolve' , + }, } gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] @@ -1224,6 +1399,15 @@ def test_convolve(): gal6b = galsim.Gaussian(sigma = 2) gsobject_compare(gal6a, gal6b) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad4') + @timer def test_list(): @@ -1296,6 +1480,38 @@ def test_list(): with assert_raises(AssertionError): gsobject_compare(gal5a, gal5c, conv=galsim.Gaussian(sigma=1)) + config = { + 'bad1' : { 'type' : 'List', + 'items' : { 'type' : 'Exponential' , 'scale_radius' : 3.4, 'flux' : 100 } }, + 'bad2' : { 'type' : 'List', 'items' : [], }, + 'bad3' : { 'type' : 'List', 'items' : 'invalid', }, + 'bad4' : { 'type' : 'List', }, + 'bad5' : { + 'type' : 'List' , + 'items' : [ { 'type' : 'Gaussian' , 'sigma' : 2 }, + { 'type' : 'Gaussian' , 'fwhm' : 2, 'flux' : 100 }, ], + 'index' : -1, + }, + 'bad6' : { + 'type' : 'List' , + 'items' : [ { 'type' : 'Gaussian' , 'sigma' : 2 }, + { 'type' : 'Gaussian' , 'fwhm' : 2, 'flux' : 100 }, ], + 'index' : 2, + }, + } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad1') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad2') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad3') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad4') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad5') + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'bad6') + @timer def test_repeat(): From da6f3ed0da3cecd350197bf24e743abe946ab1e1 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 17:49:57 -0400 Subject: [PATCH 28/96] Improve test coverage of config module: noise (#755) --- galsim/config/extra.py | 4 +-- galsim/config/noise.py | 26 +++++++----------- galsim/config/stamp.py | 5 ++++ tests/test_config_noise.py | 54 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 88f27c432df..861e642ab17 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -94,7 +94,7 @@ class OutputManager(BaseManager): pass # Make the data list the right length now to avoid issues with multiple # processes trying to append at the same time. - nimages = config['nimages'] + nimages = config.get('nimages', 1) for k in range(nimages): data.append(None) @@ -159,7 +159,7 @@ def ProcessExtraOutputsForImage(config, logger=None): image_num = config['image_num'] start_image_num = config['start_image_num'] start_obj_num = config['start_obj_num'] - nobj = config['nobj'] + nobj = config.get('nobj', [0]) k = image_num - start_image_num for i in range(k): start_obj_num += nobj[i] diff --git a/galsim/config/noise.py b/galsim/config/noise.py index f4cb1379928..136ae857374 100644 --- a/galsim/config/noise.py +++ b/galsim/config/noise.py @@ -56,14 +56,13 @@ def AddNoise(config, im, current_var=0., logger=None): logger = galsim.config.LoggerWrapper(logger) if 'noise' in config['image']: noise = config['image']['noise'] - else: - # No noise. + else: # No noise. return + if not isinstance(noise, dict): + raise galsim.GalSimConfigError("image.noise is not a dict.") - if 'type' in noise: - noise_type = noise['type'] - else: - noise_type = 'Poisson' # Default is Poisson + # 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) @@ -96,10 +95,7 @@ def CalculateNoiseVariance(config): if not isinstance(noise, dict): raise galsim.GalSimConfigError("image.noise is not a dict.") - if 'type' in noise: - noise_type = noise['type'] - else: - noise_type = 'Poisson' # 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) @@ -130,14 +126,12 @@ def AddNoiseVariance(config, im, include_obj_var=False, logger=None): logger = galsim.config.LoggerWrapper(logger) if 'noise' in config['image']: noise = config['image']['noise'] - else: - # No noise. + else: # No noise. return + if not isinstance(noise, dict): + raise galsim.GalSimConfigError("image.noise is not a dict.") - if 'type' in noise: - noise_type = noise['type'] - else: - noise_type = 'Poisson' # 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) diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index fc2c16260f4..300f155f4ee 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -841,6 +841,11 @@ def getSNRScale(self, image, config, base, logger): 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 diff --git a/tests/test_config_noise.py b/tests/test_config_noise.py index 490576c3ad5..f0f2256f00f 100644 --- a/tests/test_config_noise.py +++ b/tests/test_config_noise.py @@ -175,6 +175,60 @@ def test_poisson(): im3b = galsim.config.BuildImage(config) np.testing.assert_almost_equal(im3b.array, im3a.array, decimal=6) + # Can't have both sky_level and sky_level_pixel + config['image']['noise']['sky_level_pixel'] = 2000. + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # Must have a valid noise type + del config['image']['noise']['sky_level_pixel'] + config['image']['noise']['type'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # noise must be a dict + config['image']['noise'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # Can't have signal_to_noise and flux + config['image']['noise'] = { 'type' : 'Poisson', 'sky_level' : sky } + config['gal']['signal_to_noise'] = 100 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # This should work + del config['gal']['flux'] + galsim.config.BuildImage(config) + + # These now hit the errors in CalculateNoiseVariance rather than AddNoise + config['image']['noise']['type'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['noise'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + del config['image']['noise'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # If rather than signal_to_noise, we have an extra_weight output, then it hits + # a different error. + config['gal']['flux'] = 100 + del config['gal']['signal_to_noise'] + config['output'] = { 'weight' : {} } + config['image']['noise'] = { 'type' : 'Poisson', 'sky_level' : sky } + galsim.config.SetupExtraOutput(config) + galsim.config.SetupConfigFileNum(config, 0, 0, 0) + # This should work again. + galsim.config.BuildImage(config) + config['image']['noise']['type'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['noise'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + @timer def test_ccdnoise(): From 495c2f3a2dbcf946cb2f56c5f7884321e5aa9844 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:03:14 -0400 Subject: [PATCH 29/96] Improve test coverage of config module: whiten (#755) --- galsim/config/stamp.py | 18 +++++++++++------- tests/test_config_noise.py | 8 ++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 300f155f4ee..5a857fe9bfe 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -793,20 +793,24 @@ def whiten(self, prof, image, config, base, logger): """ # 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 prof is not None and prof.noise is not None: + 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: - if 'symmetrize' in noise: - raise galsim.GalSimConfigError( - 'Only one of whiten or symmetrize is allowed') - whiten, safe = galsim.config.ParseValue(noise, 'whiten', base, bool) + 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) - elif 'symmetrize' in noise: - symmetrize, safe = galsim.config.ParseValue(noise, 'symmetrize', base, int) + if symmetrize: current_var = prof.noise.symmetrizeImage(image, symmetrize) return current_var diff --git a/tests/test_config_noise.py b/tests/test_config_noise.py index f0f2256f00f..8974971f683 100644 --- a/tests/test_config_noise.py +++ b/tests/test_config_noise.py @@ -661,6 +661,14 @@ def test_whiten(): with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) + # Can't have both whiten and symmetrize + config['image']['noise']['variance'] = 50 + config['image']['noise']['symmetrize'] = 4 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + config['image']['noise']['symmetrize'] = False # OK if false though. + galsim.config.BuildStamp(config) + # 2. Poisson noise ##### config['image']['noise'] = { From 9294139a4e42ae56af6ffc412d73c6a306ea78a6 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:10:32 -0400 Subject: [PATCH 30/96] Improve test coverage of config module: wcs (#755) --- tests/test_config_image.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/test_config_image.py b/tests/test_config_image.py index bdd3efd546e..ab10bd48725 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1204,7 +1204,20 @@ def test_wcs(): # This needs to be done after 'scale2', so call it zref to make sure it happens # alphabetically after scale2 in a sorted list. 'zref' : '$(@image.scale2).withOrigin(galsim.PositionD(22,33))', - 'invalid' : 34 + 'bad1' : 34, + 'bad2' : { 'type' : 'Invalid' }, + 'bad3' : { 'type' : 'List', 'items' : galsim.PixelScale(0.12), }, + 'bad4' : { 'type' : 'List', 'items' : "galsim.PixelScale(0.12)", }, + 'bad5' : { + 'type' : 'List', + 'items' : [ galsim.PixelScale(0.12), galsim.PixelScale(0.23) ], + 'index' : -1 + }, + 'bad6' : { + 'type' : 'List', + 'items' : [ galsim.PixelScale(0.12), galsim.PixelScale(0.23) ], + 'index' : 2 + }, } reference = { @@ -1291,7 +1304,17 @@ def test_wcs(): assert wcs == galsim.PixelScale(1.0) with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'invalid', config) + galsim.config.BuildWCS(config['image'], 'bad1', config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], 'bad2', config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], 'bad3', config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], 'bad4', config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], 'bad5', config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], 'bad6', config) @timer def test_index_key(): From 2850c6e8645dcacf0eb1732c688897446ead2dc7 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:14:43 -0400 Subject: [PATCH 31/96] Improve test coverage of config module: image (#755) --- tests/test_config_image.py | 105 ++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/tests/test_config_image.py b/tests/test_config_image.py index ab10bd48725..290cded8b86 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -96,6 +96,47 @@ def test_single(): im7 = im7_list[k] np.testing.assert_array_equal(im7.array, im1.array) + # Check some errors + config['stamp'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['stamp'] = { 'type' : 'Invalid' } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['stamp'] = { 'draw_method' : 'invalid' } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['stamp'] = { 'n_photons' : 200 } # These next few require draw_method = phot + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['stamp'] = { 'poisson_flux' : False } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['stamp'] = { 'max_extra_noise' : 20. } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + del config['stamp'] + config['image'] = 'Invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image'] = { 'type' : 'Invalid' } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImages(3,config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImages(0,config) + config['image'] = { 'type' : 'Single', 'xsize' : 32 } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image'] = { 'type' : 'Single', 'xsize' : 0, 'ysize' : 32 } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image'] = { 'type' : 'Single' } + del config['gal'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + @timer def test_positions(): @@ -160,6 +201,13 @@ def test_positions(): np.testing.assert_array_equal(im6.array, im1.array) assert im6.bounds == im1.bounds + del config['image']['image_pos'] + del config['image']['world_pos'] + config['stamp']['world_pos'] = { 'type' : 'Random' } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + @timer def test_phot(): """Test draw_method=phot, which has extra allowed kwargs @@ -204,7 +252,7 @@ def test_phot(): # If max_extra_noise is given with n_photons, then ignore it. del config['_copied_image_keys_to_stamp'] - config['image']['max_extra_noise'] = 0.1 + config['stamp']['max_extra_noise'] = 0.1 im3c = galsim.config.BuildImage(config) np.testing.assert_array_equal(im3c.array, im3a.array) @@ -231,6 +279,19 @@ def test_phot(): im4b = galsim.config.BuildImage(config) np.testing.assert_array_equal(im4b.array, im4a.array) + # max_extra noise < 0 is invalid + config['stamp']['max_extra_noise'] = -1. + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # Also negative variance noise is invalid. + config['stamp']['max_extra_noise'] = 0.1 + config['image']['noise'] = { 'type' : 'Gaussian', 'variance' : -50 } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + @timer def test_reject(): """Test various ways that objects can be rejected. @@ -545,6 +606,44 @@ def test_ring(): print('gal1b = ',gal1b) gsobject_compare(gal1a, gal1b) + # Make sure it all runs using the normal syntax + stamp = galsim.config.BuildStamp(config) + + # num <= 0 is invalid + config['stamp']['num'] = 0 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + config['stamp']['num'] = -1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + del config['stamp']['num'] + del config['stamp']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamps(10, config) # Different error is making multiple stamps. + # Check invalid index. (Ususally this is automatic and can't be wrong, but it is + # permissible to set it by hand.) + config['stamp']['num'] = 2 + config['stamp']['index'] = 2 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + config['stamp']['index'] = -1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + # Starting on an odd index is an error. It's hard to make this happen in practice, + # but her we can do it by manually deleting the first attribute. + del galsim.config.stamp.valid_stamp_types['Ring'].first + config['stamp']['index'] = 1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + # Invalid to just have a psf. + config['psf'] = config['gal'] + del config['gal'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config) + + config = { 'stamp' : { 'type' : 'Ring', @@ -704,6 +803,10 @@ def test_scattered(): np.testing.assert_almost_equal(ixy, 0., decimal=3) np.testing.assert_almost_equal(iyy / (sigma/scale)**2, 1, decimal=1) + config['image']['index_convention'] = 'invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + # Check that stamp_xsize, stamp_ysize, image_pos use the object count, rather than the # image count. config = copy.deepcopy(base_config) From 2eebfa75ab2b664159d3a38917921652483d2086 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:18:40 -0400 Subject: [PATCH 32/96] Improve test coverage of config module: image_scattered (#755) --- galsim/config/image_scattered.py | 23 ++++++------------- galsim/config/stamp.py | 12 ++++------ tests/test_config_image.py | 39 ++++++++++++++++++++++++++++++++ tests/test_config_output.py | 1 - 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/galsim/config/image_scattered.py b/galsim/config/image_scattered.py index 1448933a22c..33c335e40de 100644 --- a/galsim/config/image_scattered.py +++ b/galsim/config/image_scattered.py @@ -52,22 +52,13 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): opt = { 'size' : int , 'xsize' : int , 'ysize' : int } params = galsim.config.GetAllParams(config, base, opt=opt, ignore=ignore+extra_ignore)[0] - # Special check for the size. Either size or both xsize and ysize is required. - if 'size' not in params: - if 'xsize' not in params or 'ysize' not in params: - raise galsim.GalSimConfigError( - "Either size or both xsize and ysize is required for image.type=Scattered") - full_xsize = params['xsize'] - full_ysize = params['ysize'] - else: - if 'xsize' in params: - raise galsim.GalSimConfigError( - "Attributes xsize is invalid if size is set for image.type=Scattered") - if 'ysize' in params: - raise galsim.GalSimConfigError( - "Attributes ysize is invalid if size is set for image.type=Scattered") - full_xsize = params['size'] - full_ysize = params['size'] + 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.stamp_xsize and image.stamp_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 diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 5a857fe9bfe..47094e05ba9 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -585,15 +585,11 @@ def ParseWorldPos(config, param_name, base, logger): @returns either a CelestialCoord or a PositionD instance. """ param = config[param_name] - if isinstance(param, dict): - value_type = galsim.config.value.valid_value_types[param.get('type','XY')][1][0] - if value_type not in [galsim.PositionD, galsim.CelestialCoord]: - raise galsim.GalSimConfigError('Invalid %s.type'%(param_name), param.get('type',None)) - return galsim.config.ParseValue(config, param_name, base, value_type)[0] + wcs = base.get('wcs', galsim.PixelScale(1.0)) # should be here, but just in case... + if wcs.isCelestial(): + return galsim.config.ParseValue(config, param_name, base, galsim.CelestialCoord)[0] else: - value_type = (galsim.CelestialCoord if type(param) == galsim.CelestialCoord - else galsim.PositionD) - return galsim.config.ParseValue(config, param_name, base, value_type)[0] + return galsim.config.ParseValue(config, param_name, base, galsim.PositionD)[0] class StampBuilder(object): """A base class for building stamp images of individual objects. diff --git a/tests/test_config_image.py b/tests/test_config_image.py index 290cded8b86..163e3d06901 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -862,6 +862,45 @@ def test_scattered(): image = galsim.config.BuildImage(config) np.testing.assert_almost_equal(image.array, image2.array) + del config['image']['size'] + del config['image']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['xsize'] = size + del config['image']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['ysize'] = size + del config['image']['_get'] + + # If doing datacube, sizes have to be consistent. + config['image_force_xsize'] = size + config['image_force_ysize'] = size + galsim.config.BuildImage(config) # This works + + # These don't. + config['image']['xsize'] = size-1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['xsize'] = size + config['image']['ysize'] = size+1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['ysize'] = size + + # Can't have both image_pos and world_pos + config['image']['world_pos'] = { + 'type' : 'List', + 'items' : [ galsim.PositionD(x1*scale, y1*scale), + galsim.PositionD(x2*scale, y2*scale), + galsim.PositionD(x3*scale, y3*scale) ] + } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + del config['image']['image_pos'] + image = galsim.config.BuildImage(config) # But just world_pos is fine. + np.testing.assert_almost_equal(image.array, image2.array) + # When starting from the file state, there is some extra code to test about this, so # check that here. config['output'] = { 'type' : 'MultiFits', 'file_name' : 'output/test_scattered.fits', diff --git a/tests/test_config_output.py b/tests/test_config_output.py index c036209d7d9..c6ad30831ff 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -1074,7 +1074,6 @@ def test_eval_full_word(): 'units': 'arcsec', 'grid_spacing': 10, 'ngrid': '$math.ceil(2*focal_rmax / @input.power_spectrum.grid_spacing)', - 'center': "0,0", }, }, From 4df29a51b4a06e87b48120bb5d09bcbabb28f2e4 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:29:57 -0400 Subject: [PATCH 33/96] Improve test coverage of config module: image_tiled (#755) --- galsim/config/image_tiled.py | 4 ++-- galsim/config/process.py | 2 +- tests/test_config_image.py | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/galsim/config/image_tiled.py b/galsim/config/image_tiled.py index 3a50ff634b0..16c3d2b8f04 100644 --- a/galsim/config/image_tiled.py +++ b/galsim/config/image_tiled.py @@ -57,9 +57,9 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): self.stamp_xsize = params.get('stamp_xsize',stamp_size) self.stamp_ysize = params.get('stamp_ysize',stamp_size) - if (self.stamp_xsize == 0) or (self.stamp_ysize == 0): + if (self.stamp_xsize <= 0) or (self.stamp_ysize <= 0): raise galsim.GalSimConfigError( - "Both image.stamp_xsize and image.stamp_ysize need to be defined and != 0.") + "Both image.stamp_xsize and image.stamp_ysize need to be defined and > 0.") border = params.get("border",0) self.xborder = params.get("xborder",border) diff --git a/galsim/config/process.py b/galsim/config/process.py index 78d0e759d21..b8a44849637 100644 --- a/galsim/config/process.py +++ b/galsim/config/process.py @@ -1063,7 +1063,7 @@ def GetRNG(config, base, logger=None, tag=''): return rng -def CleanConfig(config): # pragma: no cover +def CleanConfig(config): """Return a "clean" config dict without any leading-underscore values GalSim config dicts store a lot of ancillary information internally to help improve diff --git a/tests/test_config_image.py b/tests/test_config_image.py index 163e3d06901..a6edb2175b4 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1145,6 +1145,46 @@ def test_tiled(): im3b = galsim.config.BuildImage(config) np.testing.assert_array_equal(im3b.array, im3a.array) + # Check errors + # sizes need to be > 0 + config['image']['stamp_xsize'] = 0 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['stamp_xsize'] = xsize + config['image']['stamp_ysize'] = -30 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['stamp_ysize'] = ysize + config['image']['order'] = 'invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['order'] = 'col' + del config['image']['nx_tiles'] + del config['image']['_get'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImages(2,config) + config['image']['nx_tiles'] = nx + + # If doing datacube, sizes have to be consistent. + config['image']['stamp_xsize'] = xsize + config['image']['stamp_ysize'] = ysize + config['image_force_xsize'] = im3b.array.shape[1] + config['image_force_ysize'] = im3b.array.shape[0] + galsim.config.BuildImage(config) # This works. + + # These don't. + config['image']['stamp_xsize'] = xsize-1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['stamp_xsize'] = xsize + config['image']['stamp_ysize'] = ysize+1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['image']['stamp_ysize'] = ysize + config['image']['yborder'] = xborder + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + @timer def test_njobs(): From 69e449cc0519a58cede0ffbfd946c06cd698dd27 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 24 Apr 2018 18:53:35 -0400 Subject: [PATCH 34/96] Improve test coverage of config module: blendset (#755) --- galsim/config/stamp.py | 2 +- tests/test_config_image.py | 135 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 47094e05ba9..5debc253da8 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -732,7 +732,7 @@ def updateSkip(self, prof, image, method, offset, config, base, logger): @returns whether to skip drawing this object. """ - if prof is not None and base.get('current_image',None) is not None: + if isinstance(prof,galsim.GSObject) and base.get('current_image',None) is not None: if image is None: prof = base['wcs'].toImage(prof, image_pos=base['image_pos']) N = prof.getGoodImageSize(1.) diff --git a/tests/test_config_image.py b/tests/test_config_image.py index a6edb2175b4..4d5f8c1a1e4 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1864,6 +1864,140 @@ def test_variable_cat_size(): np.testing.assert_array_equal(cfg_images[1], ref_images[1]) +class BlendSetBuilder(galsim.config.StampBuilder): + """This is a stripped-down version of the BlendSetBuilder in examples/des/blend.py. + Use this to test the validity of having a StampBuilder that doesn't use a simple + GSObject for its "prof". + """ + + def setup(self, config, base, xsize, ysize, ignore, logger): + """Do the appropriate setup for a Blend stamp. + """ + self.ngal = galsim.config.ParseValue(config, 'n_neighbors', base, int)[0] + 1 + self.sep = galsim.config.ParseValue(config, 'sep', base, float)[0] + ignore = ignore + ['n_neighbors', 'sep'] + return super(self.__class__, self).setup(config, base, xsize, ysize, ignore, logger) + + def buildProfile(self, config, base, psf, gsparams, logger): + """ + Build a list of galaxy profiles, each convolved with the psf, to use for the blend image. + """ + if (base['obj_num'] % self.ngal != 0): + return None + else: + self.neighbor_gals = [] + for i in range(self.ngal-1): + gal = galsim.config.BuildGSObject(base, 'gal', gsparams=gsparams, logger=logger)[0] + self.neighbor_gals.append(gal) + galsim.config.RemoveCurrent(base['gal'], keep_safe=True) + + rng = galsim.config.GetRNG(config, base, logger, 'BlendSet') + ud = galsim.UniformDeviate(rng) + self.neighbor_pos = [galsim.PositionI(int(ud()*2*self.sep-self.sep), + int(ud()*2*self.sep-self.sep)) + for i in range(self.ngal-1)] + #print('neighbor positions = ',self.neighbor_pos) + + self.main_gal = galsim.config.BuildGSObject(base, 'gal', gsparams=gsparams, + logger=logger)[0] + + self.profiles = [ self.main_gal ] + self.profiles += [ g.shift(p) for g, p in zip(self.neighbor_gals, self.neighbor_pos) ] + if psf: + self.profiles = [ galsim.Convolve(gal, psf) for gal in self.profiles ] + return self.profiles + + def draw(self, profiles, image, method, offset, config, base, logger): + nx = base['stamp_xsize'] + ny = base['stamp_ysize'] + wcs = base['wcs'] + + if profiles is not None: + bounds = galsim.BoundsI(galsim.PositionI(0,0)) + for pos in self.neighbor_pos: + bounds += pos + bounds = bounds.withBorder(max(nx,ny)//2 + 1) + + self.full_images = [] + for prof in profiles: + im = galsim.ImageF(bounds=bounds, wcs=wcs) + galsim.config.DrawBasic(prof, im, method, offset-im.true_center, config, base, + logger) + self.full_images.append(im) + + k = base['obj_num'] % self.ngal + if k == 0: + center_pos = galsim.PositionI(0,0) + else: + center_pos = self.neighbor_pos[k-1] + xmin = int(center_pos.x) - nx//2 + 1 + ymin = int(center_pos.y) - ny//2 + 1 + self.bounds = galsim.BoundsI(xmin, xmin+nx-1, ymin, ymin+ny-1) + + image.setZero() + image.wcs = wcs + for full_im in self.full_images: + assert full_im.bounds.includes(self.bounds) + image += full_im[self.bounds] + + return image + +@timer +def test_blend(): + """Test the functionality used by the BlendSet stamp type in examples/des/blend.py. + Especially that it's internal "prof" is not just a single GSObject. + """ + galsim.config.RegisterStampType('BlendSet', BlendSetBuilder()) + config = { + 'stamp' : { + 'type' : 'BlendSet', + 'n_neighbors' : 3, + 'sep' : 10, + 'size' : 64, + }, + 'gal' : { + 'type' : 'Gaussian', + 'sigma' : { 'type' : 'Random', 'min': 1, 'max': 3 }, + 'flux' : { 'type' : 'Random', 'min': 20, 'max': 300 }, + }, + 'image' : { + 'type' : 'Single', + 'pixel_scale' : 0.5, + 'random_seed' : 1234, + }, + } + + # First just check that this works correctly as is. + galsim.config.SetupConfigImageNum(config, 0, 0) + images = galsim.config.BuildImages(8,config) + for i, im in enumerate(images): + im.write('output/blend%02d.fits'%i) + # Within each blendset, the images are shifted copies of each other. + np.testing.assert_array_equal(images[1].array[3:64,16:64], images[0].array[0:61,9:57]) + np.testing.assert_array_equal(images[2].array[1:62,6:54], images[0].array[0:61,9:57]) + np.testing.assert_array_equal(images[3].array[0:61,0:48], images[0].array[0:61,9:57]) + + np.testing.assert_array_equal(images[5].array[0:55,9:64], images[4].array[9:64,9:64]) + np.testing.assert_array_equal(images[6].array[5:60,1:56], images[4].array[9:64,9:64]) + np.testing.assert_array_equal(images[7].array[0:55,0:55], images[4].array[9:64,9:64]) + + # If there is a current_image, then updateSkip requires special handling here. + config['current_image'] = galsim.Image(64,64) + im8 = galsim.config.BuildStamp(config, obj_num=8) + + # Some reject items are invalid when using this kind of stamp. + config['stamp']['min_flux_frac'] = 0.3 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config, obj_num=8) + del config['stamp']['min_flux_frac'] + config['stamp']['min_snr'] = 20 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config, obj_num=8) + del config['stamp']['min_snr'] + config['stamp']['max_snr'] = 200 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildStamp(config, obj_num=8) + if __name__ == "__main__": test_single() @@ -1881,3 +2015,4 @@ def test_variable_cat_size(): test_multirng() test_template() test_variable_cat_size() + test_blend() From f0aa0de1b33b98847307ae9a521bc755aac95dc7 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 25 Apr 2018 10:11:58 -0400 Subject: [PATCH 35/96] Improve test coverage of config module: power_spectrum (#755) --- tests/test_config_image.py | 110 ++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/tests/test_config_image.py b/tests/test_config_image.py index 4d5f8c1a1e4..15f767783eb 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1047,7 +1047,7 @@ def test_tiled(): 'type' : 'Gaussian', 'sigma' : { 'type': 'Random', 'min': 1, 'max': 2 }, 'flux' : '$image_pos.x + image_pos.y', - } + }, } seed = 1234 @@ -1185,6 +1185,114 @@ def test_tiled(): with assert_raises(galsim.GalSimConfigError): galsim.config.BuildImage(config) + # If tile with a square grid, then PowerSpectrum can omit grid_spacing and ngrid. + size = 32 + config = { + 'image' : { + 'type' : 'Tiled', + 'nx_tiles' : nx, + 'ny_tiles' : ny, + 'stamp_size' : size, + 'pixel_scale' : scale, + + 'random_seed' : 1234, + }, + 'gal' : { + 'type' : 'Gaussian', + 'sigma' : { 'type': 'Random', 'min': 1, 'max': 2 }, + 'flux' : '$image_pos.x + image_pos.y', + 'shear' : { 'type' : 'PowerSpectrumShear' }, + }, + 'input' : { + 'power_spectrum' : { 'e_power_function' : 'np.exp(-k**0.2)' }, + }, + } + + seed = 1234 + ps = galsim.PowerSpectrum(e_power_function=lambda k: np.exp(-k**0.2)) + rng = galsim.BaseDeviate(seed) + im4a = galsim.Image(nx*size, ny*size, scale=scale) + center = im4a.true_center * scale + ps.buildGrid(grid_spacing=size*scale, ngrid=max(nx,ny), rng=rng, center=center) + for j in range(ny): + for i in range(nx): + seed += 1 + ud = galsim.UniformDeviate(seed) + xorigin = i * size + 1 + yorigin = j * size + 1 + x = xorigin + (size-1)/2. + y = yorigin + (size-1)/2. + stamp = galsim.Image(size,size, scale=scale) + stamp.setOrigin(xorigin,yorigin) + + sigma = ud() + 1 + flux = x + y + gal = galsim.Gaussian(sigma=sigma, flux=flux) + g1, g2 = ps.getShear(galsim.PositionD(x*scale,y*scale)) + gal = gal.shear(g1=g1, g2=g2) + gal.drawImage(stamp) + im4a[stamp.bounds] = stamp + + # Compare to what config builds + im4b = galsim.config.BuildImage(config) + np.testing.assert_array_equal(im4b.array, im4a.array) + + # If grid sizes aren't square, it also works properly, but with more complicated ngrid calc. + config = galsim.config.CleanConfig(config) + del config['image']['stamp_size'] + config['image']['stamp_xsize'] = xsize + config['image']['stamp_ysize'] = ysize + seed = 1234 + rng = galsim.BaseDeviate(seed) + im5a = galsim.Image(nx*xsize, ny*ysize, scale=scale) + center = im5a.true_center * scale + grid_spacing = min(xsize,ysize) * scale + ngrid = int(math.ceil(max(nx*xsize, ny*ysize) * scale / grid_spacing)) + ps.buildGrid(grid_spacing=grid_spacing, ngrid=ngrid, rng=rng, center=center) + for j in range(ny): + for i in range(nx): + seed += 1 + ud = galsim.UniformDeviate(seed) + xorigin = i * xsize + 1 + yorigin = j * ysize + 1 + x = xorigin + (xsize-1)/2. + y = yorigin + (ysize-1)/2. + stamp = galsim.Image(xsize,ysize, scale=scale) + stamp.setOrigin(xorigin,yorigin) + + sigma = ud() + 1 + flux = x + y + gal = galsim.Gaussian(sigma=sigma, flux=flux) + g1, g2 = ps.getShear(galsim.PositionD(x*scale,y*scale)) + gal = gal.shear(g1=g1, g2=g2) + gal.drawImage(stamp) + im5a[stamp.bounds] = stamp + + im5b = galsim.config.BuildImage(config) + np.testing.assert_array_equal(im5b.array, im5a.array) + + # Finally, if the image type isn't tiled, then grid_spacing is required. + config = { + 'image' : { + 'type' : 'Scattered', + 'size': nx*size, + 'nobjects' : nx*ny, + 'pixel_scale' : scale, + 'random_seed' : 1234, + }, + 'gal' : { + 'type' : 'Gaussian', + 'sigma' : { 'type': 'Random', 'min': 1, 'max': 2 }, + 'flux' : '$image_pos.x + image_pos.y', + 'shear' : { 'type' : 'PowerSpectrumShear' }, + }, + 'input' : { + 'power_spectrum' : { 'e_power_function' : 'np.exp(-k**0.2)' }, + }, + } + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + @timer def test_njobs(): From 9090029bd4382a83c22db234ca3ca2fee0d95f56 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 25 Apr 2018 11:42:55 -0400 Subject: [PATCH 36/96] Improve test coverage of config module: input (#755) --- galsim/config/input.py | 38 ++++++++++++++++++------------------- galsim/config/process.py | 21 ++++++++++++-------- tests/test_config_image.py | 34 +++++++++++++++++++++++++++++---- tests/test_config_output.py | 17 +++++++++++++---- tests/test_config_value.py | 21 +++++++++++++++----- tests/test_des.py | 2 +- 6 files changed, 92 insertions(+), 41 deletions(-) diff --git a/galsim/config/input.py b/galsim/config/input.py index 81b11cd3f02..55ef564e9c5 100644 --- a/galsim/config/input.py +++ b/galsim/config/input.py @@ -46,12 +46,12 @@ def ProcessInput(config, logger=None, file_scope_only=False, safe_only=False): any objects that need to be initialized. Each item registered as a valid input type will be built and available at the top level - of config in config['input_objs']. Since there is allowed to be more than one of each type + of config in config['_input_objs']. Since there is allowed to be more than one of each type of input object (e.g. multilpe catalogs or multiple dicts), these are actually lists. If there is only one e.g. catalog entry in config['input'], then this list will have one element. - e.g. config['input_objs']['catalog'][0] holds the first catalog item defined in + e.g. config['_input_objs']['catalog'][0] holds the first catalog item defined in config['input']['catalog'] (if any). @param config The configuration dict to process @@ -97,7 +97,7 @@ def ProcessInput(config, logger=None, file_scope_only=False, safe_only=False): ('output' in config and 'nproc' in config['output'] and galsim.config.ParseValue(config['output'], 'nproc', config, int)[0] != 1) ) ) - if use_manager and 'input_manager' not in config: + if use_manager and '_input_manager' not in config: from multiprocessing.managers import BaseManager class InputManager(BaseManager): pass @@ -109,16 +109,16 @@ class InputManager(BaseManager): pass tag = key + str(i) InputManager.register(tag, valid_input_types[key].init_func) # Start up the input_manager - config['input_manager'] = InputManager() - config['input_manager'].start() + config['_input_manager'] = InputManager() + config['_input_manager'].start() - if 'input_objs' not in config: - config['input_objs'] = {} + if '_input_objs' not in config: + config['_input_objs'] = {} for key in all_keys: fields = config['input'][key] nfields = len(fields) if isinstance(fields, list) else 1 - config['input_objs'][key] = [ None for i in range(nfields) ] - config['input_objs'][key+'_safe'] = [ None for i in range(nfields) ] + config['_input_objs'][key] = [ None for i in range(nfields) ] + config['_input_objs'][key+'_safe'] = [ None for i in range(nfields) ] # Read all input fields provided and create the corresponding object # with the parameters given in the config file. @@ -136,8 +136,8 @@ class InputManager(BaseManager): pass for i in range(len(fields)): field = fields[i] - input_objs = config['input_objs'][key] - input_objs_safe = config['input_objs'][key+'_safe'] + input_objs = config['_input_objs'][key] + input_objs_safe = config['_input_objs'][key+'_safe'] logger.debug('file %d: Current values for %s are %s, safe = %s', file_num, key, str(input_objs[i]), input_objs_safe[i]) if input_objs[i] is not None and input_objs_safe[i]: @@ -172,7 +172,7 @@ class InputManager(BaseManager): pass logger.debug('file %d: %s kwargs = %s',file_num,key,kwargs) if use_manager: tag = key + str(i) - input_obj = getattr(config['input_manager'],tag)(**kwargs) + input_obj = getattr(config['_input_manager'],tag)(**kwargs) else: input_obj = loader.init_func(**kwargs) @@ -208,7 +208,7 @@ def SetupInput(config, logger=None): at BuildImage say. This will make sure the input objects are set up in the way that they normally would have been by the first level of processing in a `galsim config_file` run. """ - if 'input_objs' not in config: + if '_input_objs' not in config: orig_index_key = config.get('index_key',None) config['index_key'] = 'file_num' ProcessInput(config, logger=logger) @@ -246,8 +246,8 @@ def ProcessInputNObjects(config, logger=None): # If it's a list, just use the first one. if isinstance(field, list): field = field[0] - if key in config['input_objs'] and config['input_objs'][key+'_safe'][0]: - input_obj = config['input_objs'][key][0] + if key in config['_input_objs'] and config['_input_objs'][key+'_safe'][0]: + input_obj = config['_input_objs'][key][0] else: kwargs, safe = loader.getKwargs(field, config, logger) kwargs['_nobjects_only'] = True @@ -271,7 +271,7 @@ def SetupInputsForImage(config, logger=None): loader = valid_input_types[key] if key in config['input']: fields = config['input'][key] - input_objs = config['input_objs'][key] + input_objs = config['_input_objs'][key] # Make fields a list if necessary. if not isinstance(fields, list): fields = [ fields ] @@ -292,7 +292,7 @@ def GetInputObj(input_type, config, base, param_name): @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']: + 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)) @@ -303,11 +303,11 @@ def GetInputObj(input_type, config, base, param_name): if num < 0: raise galsim.GalSimConfigValueError("Invalid num < 0 supplied for %s."%param_name, num) - if num >= len(base['input_objs'][input_type]): + 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] + return base['_input_objs'][input_type][num] class InputLoader(object): diff --git a/galsim/config/process.py b/galsim/config/process.py index b8a44849637..0836617c663 100644 --- a/galsim/config/process.py +++ b/galsim/config/process.py @@ -258,7 +258,7 @@ def CopyConfig(config): config1 = copy.copy(config) # Make sure the input_manager isn't in the copy - config1.pop('input_manager',None) + config1.pop('_input_manager',None) # Now deepcopy all the regular config fields to make sure things like current don't # get clobbered by two processes writing to the same dict. Also the rngs. @@ -1039,14 +1039,18 @@ def GetRNG(config, base, logger=None, tag=''): index, index_key = GetIndex(config, base) logger.debug("GetRNG for %s: %s",index_key,index) - if 'rng_num' in config: - rng_num = config['rng_num'] + 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) - if not (index_key + '_rngs') in base: + 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") - rng = base.get(index_key + '_rngs', None)[int(rng_num)] + 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) @@ -1063,7 +1067,7 @@ def GetRNG(config, base, logger=None, tag=''): return rng -def CleanConfig(config): +def CleanConfig(config, keep_current=False): """Return a "clean" config dict without any leading-underscore values GalSim config dicts store a lot of ancillary information internally to help improve @@ -1076,8 +1080,9 @@ def CleanConfig(config): >>> print(galsim.config.CleanConfig(config_dict)) """ if isinstance(config, dict): - return { k : CleanConfig(config[k]) for k in config if k[0] != '_' } + return { k : CleanConfig(config[k], keep_current) for k in config + if k[0] != '_' and (keep_current or k != 'current') } elif isinstance(config, list): - return [ CleanConfig(item) for item in config ] + return [ CleanConfig(item, keep_current) for item in config ] else: return config diff --git a/tests/test_config_image.py b/tests/test_config_image.py index 15f767783eb..dcdd2f913fd 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -857,8 +857,7 @@ def test_scattered(): # However, an input field that does have nobj will return something for nobjects. # This catalog has 3 rows, so equivalent to nobjects = 3 config['input'] = { 'catalog' : { 'dir' : 'config_input', 'file_name' : 'catalog.txt' } } - del config['input_objs'] - galsim.config.RemoveCurrent(config) + config = galsim.config.CleanConfig(config) image = galsim.config.BuildImage(config) np.testing.assert_almost_equal(image.array, image2.array) @@ -905,8 +904,7 @@ def test_scattered(): # check that here. config['output'] = { 'type' : 'MultiFits', 'file_name' : 'output/test_scattered.fits', 'nimages' : 2 } - del config['input_objs'] - galsim.config.RemoveCurrent(config) + config = galsim.config.CleanConfig(config) galsim.config.BuildFile(config) image = galsim.fits.read('output/test_scattered.fits') np.testing.assert_almost_equal(image.array, image2.array) @@ -1758,6 +1756,11 @@ def test_index_key(): assert 'current' not in config1['gal']['ellip'] assert 'current' not in config1['gal']['shear'] + # Finally check for invalid index_key + config['psf']['index_key'] = 'psf_num' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildFile(config) + @timer def test_multirng(): @@ -1847,6 +1850,29 @@ def test_multirng(): np.testing.assert_array_equal(im.array, images2[n].array) np.testing.assert_array_equal(im.array, images3[n].array) + # Finally, test invalid rng_num + config4 = galsim.config.CopyConfig(config) + config4['image']['world_pos']['rng_num'] = -1 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config4) + config5 = galsim.config.CopyConfig(config) + config5['image']['world_pos']['rng_num'] = 20 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config5) + config6 = galsim.config.CopyConfig(config) + config6['image']['world_pos']['rng_num'] = 1.3 + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config6) + config7 = galsim.config.CopyConfig(config) + config7['image']['world_pos']['rng_num'] = 1 + config7['image']['random_seed'] = 12345 + del config7['input'] + del config7['psf']['ellip'] + del config7['gal']['shear'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config7) + + @timer def test_template(): """Test various uses of the template keyword diff --git a/tests/test_config_output.py b/tests/test_config_output.py index c6ad30831ff..a9206967e12 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -192,7 +192,7 @@ def test_multifits(): # However, an input field that does have nobj will return something for nobjects. # This catalog has 3 rows, so equivalent to nobjects = 3 - del config['input_objs'] + config = galsim.config.CleanConfig(config) config['input'] = { 'catalog' : { 'dir' : 'config_input', 'file_name' : 'catalog.txt' } } galsim.config.BuildFile(config) im4_list = galsim.fits.readMulti('output/test_multifits.fits') @@ -260,7 +260,7 @@ def test_datacube(): # However, an input field that does have nobj will return something for nobjects. # This catalog has 3 rows, so equivalent to nobjects = 3 - del config['input_objs'] + config = galsim.config.CleanConfig(config) config['input'] = { 'catalog' : { 'dir' : 'config_input', 'file_name' : 'catalog.txt' } } galsim.config.BuildFile(config) im4_list = galsim.fits.readCube('output/test_datacube.fits') @@ -374,6 +374,15 @@ def test_skip(): assert "Splitting work into 3 jobs. Doing job 3" in cl.output assert "Building 2 out of 6 total files: file_num = 4 .. 5" in cl.output + # job < 1 or job > njobs is invalid + with assert_raises(galsim.GalSimValueError): + galsim.config.Process(config, njobs=3, job=0) + with assert_raises(galsim.GalSimValueError): + galsim.config.Process(config, njobs=3, job=4) + # Also njobs < 1 is invalid + with assert_raises(galsim.GalSimValueError): + galsim.config.Process(config, njobs=0) + @timer def test_extra_wt(): @@ -939,7 +948,7 @@ def test_config(): assert config == config5 # Copying deep copies and removes any existing input_manager - config4['input_manager'] = 'an input manager' + config4['_input_manager'] = 'an input manager' config7 = galsim.config.CopyConfig(config4) assert config == config7 @@ -1182,7 +1191,7 @@ def test_eval_full_word(): logger = logging.getLogger('test_eval_full_word') logger.addHandler(logging.StreamHandler(sys.stdout)) logger.setLevel(logging.DEBUG) - galsim.config.Process(config, logger=logger) + galsim.config.Process(config, logger=logger, except_abort=True) # First check the truth catalogs data0 = np.genfromtxt('output/test_eval_full_word_0.dat', names=True, deletechars='') diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 8126703e376..2bded8b8fc3 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -394,8 +394,7 @@ def test_float_value(): ps = galsim.PowerSpectrum(e_power_function='500 * np.exp(-k**0.2)') ps.buildGrid(grid_spacing=10, ngrid=20, interpolant='linear', rng=rng) print("strong lensing mag = ",ps.getMagnification((0.1,0.2))) - galsim.config.RemoveCurrent(config) - del config['input_objs'] + config = galsim.config.CleanConfig(config) config['input']['power_spectrum']['e_power_function'] = '500 * np.exp(-k**0.2)' with CaptureLog() as cl: galsim.config.SetupInputsForImage(config, logger=cl.logger) @@ -526,7 +525,9 @@ def test_int_value(): 'cur2' : { 'type' : 'Current', 'key' : 'list2.index.step' }, 'bad1' : 'left', 'bad2' : int, - 'bad3' : { 'type' : 'Current', 'key' : 'list2.index.type' } + 'bad3' : { 'type' : 'Current', 'key' : 'list2.index.type' }, + 'bad4' : { 'type' : 'Catalog' , 'num' : 2, 'col' : 'int1' }, + 'bad5' : { 'type' : 'Catalog' , 'num' : -1, 'col' : 'int1' }, } test_yaml = True @@ -703,6 +704,17 @@ def test_int_value(): galsim.config.ParseValue(config,'bad2', config, int) with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config,'bad3',config, int) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad4',config, int) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'bad5',config, int) + config = galsim.config.CleanConfig(config) + del config['input']['catalog'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'cat1',config, int) + del config['input'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'cat1',config, int) @timer @@ -1304,8 +1316,7 @@ def test_shear_value(): ps.buildGrid(grid_spacing=10, ngrid=40, center=galsim.PositionD(5,5), interpolant='linear', rng=rng) print("strong lensing shear = ",ps.getShear((0.1,0.2))) - galsim.config.RemoveCurrent(config) - del config['input_objs'] + config = galsim.config.CleanConfig(config) config['input']['power_spectrum']['e_power_function'] = '500 * np.exp(-k**0.2)' galsim.config.SetupInputsForImage(config, None) ps2b = ps.getShear((0.1,0.2)) diff --git a/tests/test_des.py b/tests/test_des.py index 4c16fab9cb0..71c463d9f0d 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -666,7 +666,7 @@ def test_psf_config(): # Insert a wcs for thes last one. config['wcs'] = galsim.FitsWCS(os.path.join(data_dir,wcs_file)) - del config['input_objs'] + config = galsim.config.CleanConfig(config) galsim.config.ProcessInput(config) psfex2 = galsim.des.DES_PSFEx(psfex_file, dir=data_dir, wcs=config['wcs']) psf5a = galsim.config.BuildGSObject(config, 'psf5')[0] From d3f7ed293b5d08dde638095599ac93d6734a19be Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 25 Apr 2018 20:41:54 -0400 Subject: [PATCH 37/96] Improve test coverage of config module: extra_psf (#755) --- galsim/config/extra.py | 35 +++++---- galsim/config/extra_psf.py | 16 +++- tests/test_config_output.py | 147 +++++++++++++++++++++++++++++++++++- 3 files changed, 178 insertions(+), 20 deletions(-) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 861e642ab17..5510403b1bb 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -115,6 +115,8 @@ def SetupExtraOutputsForImage(config, logger=None): @param logger If given, a logger object to log progress. [default: None] """ if 'output' in config: + if 'extra_builder' not in config: + SetupExtraOutput(config, logger) for key in [ k for k in valid_extra_outputs.keys() if k in config['output'] ]: builder = config['extra_builder'][key] field = config['output'][key] @@ -153,13 +155,13 @@ def ProcessExtraOutputsForImage(config, logger=None): if 'output' in config: obj_nums = None for key in [ k for k in valid_extra_outputs.keys() if k in config['output'] ]: + image_num = config.get('image_num',0) + start_image_num = config.get('start_image_num',0) if obj_nums is None: # Figure out which obj_nums were used for this image. - file_num = config['file_num'] - image_num = config['image_num'] - start_image_num = config['start_image_num'] - start_obj_num = config['start_obj_num'] - nobj = config.get('nobj', [0]) + file_num = config.get('file_num',0) + start_obj_num = config.get('start_obj_num',0) + nobj = config.get('nobj', [1]) k = image_num - start_image_num for i in range(k): start_obj_num += nobj[i] @@ -169,7 +171,7 @@ def ProcessExtraOutputsForImage(config, logger=None): obj_nums = [ n for n in obj_nums if n not in skipped ] builder = config['extra_builder'][key] field = config['output'][key] - index = config['image_num'] - config['start_image_num'] + index = image_num - start_image_num builder.processImage(index, obj_nums, field, config, logger) @@ -209,7 +211,7 @@ def WriteExtraOutputs(config, main_data, logger=None): if 'file_name' in field: galsim.config.SetDefaultExt(field, '.fits') file_name = galsim.config.ParseValue(field,'file_name',config,str)[0] - else: # pragma: no cover + else: # If no file_name, then probably writing to hdu continue if 'dir' in field: @@ -222,7 +224,7 @@ def WriteExtraOutputs(config, main_data, logger=None): galsim.config.EnsureDir(file_name) - if noclobber and os.path.isfile(file_name): # pragma: no cover + if noclobber and os.path.isfile(file_name): logger.warning('Not writing %s file %d = %s because output.noclobber = True ' 'and file exists',key,config['file_num'],file_name) continue @@ -269,7 +271,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): field = output[key] if 'hdu' in field: hdu = galsim.config.ParseValue(field,'hdu',config,int)[0] - else: # pragma: no cover + else: # If no hdu, then probably writing to file continue if hdu <= 0 or hdu in hdus: @@ -307,12 +309,12 @@ def CheckNoExtraOutputHDUs(config, output_type, logger=None): "Output type %s cannot add extra images as HDUs"%output_type) -def GetFinalExtraOutput(key, config, main_data, logger=None): +def GetFinalExtraOutput(key, config, main_data=[], logger=None): """Get the finalized output object for the given extra output key @param key The name of the output field in config['output'] @param config The configuration dict. - @param main_data The main file data in case it is needed. + @param main_data The main file data in case it is needed. [default: []] @param logger If given, a logger object to log progress. [default: None] @returns the final data to be output. @@ -433,7 +435,7 @@ def ensureFinalized(self, config, base, main_data, logger): @returns the final version of the object. """ - if self.final_data is None: # pragma: no branch + if self.final_data is None: self.final_data = self.finalize(config, base, main_data, logger) return self.final_data @@ -480,12 +482,9 @@ def writeHdu(self, config, base, logger): @returns an HDU with the output data. """ - n = len(self.data) - if n == 0: - raise galsim.GalSimConfigError("No %s images were created."%self._extra_output_key) - elif n > 1: - raise galsim.GalSimConfigError( - "%d %s images were created, but expecting only 1."%(n,self._extra_output_key)) + if len(self.data) != 1: # pragma: no cover (Not sure if this is possible.) + raise galsim.GalSimError( + "%d %s images were created. Expecting 1."%(n,self._extra_output_key)) return self.data[0] diff --git a/galsim/config/extra_psf.py b/galsim/config/extra_psf.py index 1a51c2241f6..8de22ea8518 100644 --- a/galsim/config/extra_psf.py +++ b/galsim/config/extra_psf.py @@ -38,11 +38,25 @@ def DrawPSFStamp(psf, config, base, bounds, offset, method, logger): 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) + 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") diff --git a/tests/test_config_output.py b/tests/test_config_output.py index a9206967e12..d2c5e2f689d 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -412,14 +412,53 @@ def test_extra_wt(): galsim.config.Process(config) + main_im = [ galsim.fits.read('output/test_main_%d.fits'%k) for k in range(nfiles) ] for k in range(nfiles): im_wt = galsim.fits.read('output/test_wt_%d.fits'%k) np.testing.assert_almost_equal(im_wt.array, 1./(0.7 + k)) im_bp = galsim.fits.read('output/test_bp_%d.fits'%k) np.testing.assert_array_equal(im_bp.array, 0) + os.remove('output/test_main_%d.fits'%k) + + # If noclobber = True, don't overwrite existing file. + config['noise'] = { 'type' : 'Poisson', 'sky_level_pixel' : 500 } + config['output']['noclobber'] = True + galsim.config.RemoveCurrent(config) + with CaptureLog() as cl: + galsim.config.Process(config, logger=cl.logger) + assert 'Not writing weight file 0 = output/test_wt_0.fits' in cl.output + for k in range(nfiles): + im = galsim.fits.read('output/test_main_%d.fits'%k) + np.testing.assert_equal(im.array, main_im[k].array) + im_wt = galsim.fits.read('output/test_wt_%d.fits'%k) + np.testing.assert_almost_equal(im_wt.array, 1./(0.7 + k)) + + # Can also add these as extra hdus rather than separate files. + config['output']['noclobber'] = False + config['output']['weight'] = { 'hdu' : 1 } + config['output']['badpix'] = { 'hdu' : 2 } + galsim.config.RemoveCurrent(config) + galsim.config.Process(config) + for k in range(nfiles): + im_wt = galsim.fits.read('output/test_main_%d.fits'%k, hdu=1) + np.testing.assert_almost_equal(im_wt.array, 1./(0.7 + k)) + im_bp = galsim.fits.read('output/test_main_%d.fits'%k, hdu=2) + np.testing.assert_array_equal(im_bp.array, 0) + + config['output']['badpix'] = { 'hdu' : 0 } + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.Process(config, except_abort=True) + config['output']['badpix'] = { 'hdu' : 1 } + with assert_raises(galsim.GalSimConfigError): + galsim.config.Process(config, except_abort=True) + config['output']['badpix'] = { 'hdu' : 3 } + with assert_raises(galsim.GalSimConfigError): + galsim.config.Process(config, except_abort=True) # If include_obj_var = True, then weight image includes signal. config['output']['weight']['include_obj_var'] = True + config['output']['badpix'] = { 'hdu' : 2 } config['output']['nproc'] = 2 galsim.config.RemoveCurrent(config) galsim.config.Process(config) @@ -428,9 +467,35 @@ def test_extra_wt(): sigma = ud() + 1. gal = galsim.Gaussian(sigma=sigma, flux=100) im = gal.drawImage(scale=0.4) - im_wt = galsim.fits.read('output/test_wt_%d.fits'%k) + im_wt = galsim.fits.read('output/test_main_%d.fits'%k, hdu=1) np.testing.assert_almost_equal(im_wt.array, 1./(0.7 + k + im.array)) + # It is permissible for weight, badpix to have no output. Some use cases require building + # the weight and/or badpix information even if it is not associated with any output. + config['output']['weight'] = {} + config['output']['badpix'] = {} + galsim.config.RemoveCurrent(config) + galsim.config.Process(config) + for k in range(nfiles): + assert_raises(OSError, galsim.fits.read, 'output/test_main_%d.fits'%k, hdu=1) + os.remove('output/test_wt_%d.fits'%k) + os.remove('output/test_main_%d.fits'%k) + + # Can also have both outputs + config['output']['weight'] = { 'file_name': "$'output/test_wt_%d.fits'%file_num", 'hdu': 1 } + galsim.config.RemoveCurrent(config) + galsim.config.Process(config, except_abort=True) + for k in range(nfiles): + im_wt1 = galsim.fits.read('output/test_wt_%d.fits'%k) + np.testing.assert_almost_equal(im_wt1.array, 1./(0.7 + k)) + im_wt2 = galsim.fits.read('output/test_main_%d.fits'%k, hdu=1) + np.testing.assert_almost_equal(im_wt2.array, 1./(0.7 + k)) + + # Other such use cases would access the final weight or badpix image using GetFinalExtraOutput + galsim.config.BuildFile(config) + wt = galsim.config.extra.GetFinalExtraOutput('weight', config) + np.testing.assert_almost_equal(wt[0].array, 1./0.7) + # If the image is a Scattered type, then the weight adn badpix images are built by a # different code path. config = { @@ -707,6 +772,85 @@ def test_extra_psf(): assert "Not writing psf file 4 = output_psf/test_psf.fits because already written" in cl.output assert "Not writing psf file 5 = output_psf/test_psf.fits because already written" in cl.output +@timer +def test_extra_psf_sn(): + """Test the signal_to_noise option of the extra psf field + """ + config = { + 'image' : { + 'random_seed' : 1234, + 'pixel_scale' : 0.4, + 'size' : 64, + }, + 'gal' : { + 'type' : 'Gaussian', + 'sigma' : 2.3, + 'flux' : 100, + }, + 'psf' : { + 'type' : 'Moffat', + 'beta' : 3.5, + 'fwhm' : 0.7, + }, + 'output' : { + 'psf' : {} + }, + } + # First pure psf image with no noise. + gal_image = galsim.config.BuildImage(config) + pure_psf_image = galsim.config.extra.GetFinalExtraOutput('psf', config)[0] + np.testing.assert_almost_equal(pure_psf_image.array.sum(), 1.) + + # Draw PSF at S/N = 100 + # (But first check that an error is raised if noise is missing. + config['output']['psf']['signal_to_noise'] = 100 + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + noise_var = 20. + config['image']['noise'] = { 'type' : 'Gaussian', 'variance' : noise_var, } + gal_image = galsim.config.BuildImage(config) + sn100_psf_image = galsim.config.extra.GetFinalExtraOutput('psf', config)[0] + sn100_flux = sn100_psf_image.array.sum() + psf_noise = sn100_psf_image - sn100_flux * pure_psf_image + print('psf_noise.var = ',psf_noise.array.var(), noise_var) + np.testing.assert_allclose(psf_noise.array.var(), noise_var, rtol=0.02) + snr = np.sqrt( np.sum(sn100_psf_image.array**2, dtype=float) / noise_var ) + print('snr = ',snr, 100) + np.testing.assert_allclose(snr, 100, rtol=0.2) # Not super accurate for any single image. + + # Can also specify different draw_methods. + config['output']['psf']['draw_method'] = 'real_space' + galsim.config.RemoveCurrent(config) + gal_image = galsim.config.BuildImage(config) + real_psf_image = galsim.config.extra.GetFinalExtraOutput('psf', config)[0] + print('real flux = ', real_psf_image.array.sum(), sn100_flux) + np.testing.assert_allclose(real_psf_image.array.sum(), sn100_flux, rtol=1.e-4) + + # phot is invalid with signal_to_noise + config['output']['psf']['draw_method'] = 'phot' + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # Check for other invalid input + config['output']['psf']['draw_method'] = 'input' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + config['output']['psf']['draw_method'] = 'auto' + config['output']['psf']['flux'] = sn100_flux + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildImage(config) + + # OK to use phot with flux. + config['output']['psf']['draw_method'] = 'phot' + del config['output']['psf']['signal_to_noise'] + gal_image = galsim.config.BuildImage(config) + phot_psf_image = galsim.config.extra.GetFinalExtraOutput('psf', config)[0] + print('phot flux = ', phot_psf_image.array.sum(), sn100_flux) + np.testing.assert_allclose(phot_psf_image.array.sum(), sn100_flux, rtol=1.e-4) + @timer def test_extra_truth(): @@ -1253,6 +1397,7 @@ def test_eval_full_word(): test_skip() test_extra_wt() test_extra_psf() + test_extra_psf_sn() test_extra_truth() test_retry_io() test_config() From 23b3aeb78fa9782a3ebab3d2249766c6eab59053 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 25 Apr 2018 21:03:50 -0400 Subject: [PATCH 38/96] Improve test coverage of config module: output (#755) --- galsim/config/output.py | 6 +++--- tests/test_config_output.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/galsim/config/output.py b/galsim/config/output.py index 1c045476ff9..ec22d38ca8f 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -118,9 +118,9 @@ def BuildFiles(nfiles, config, file_num=0, logger=None, except_abort=False): def done_func(logger, proc, k, result, t2): file_num, file_name = info[k] file_name2, t = result # This is the t for which 0 means the file was skipped. - if file_name2 != file_name: - raise galsim.GalSimConfigError("Files seem to be out of sync. %s != %s", - file_name, file_name2) + if file_name2 != file_name: # pragma: no cover (I think this should never happen.) + raise galsim.GalSimError("Files seem to be out of sync. %s != %s", + file_name, file_name2) if t != 0 and logger: if proc is None: s0 = '' else: s0 = '%s: '%proc diff --git a/tests/test_config_output.py b/tests/test_config_output.py index d2c5e2f689d..1bacc013d0e 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -128,10 +128,31 @@ def test_fits(): # multithreading starts. But with a regular logger, there really is profiling output. assert "Starting separate profiling for each of the" in cl.output - # If there is no output field, the default behavior is to write to root.fits. + # Check some public API utility functions + assert galsim.config.GetNFiles(config) == 6 + assert galsim.config.GetNImagesForFile(config, 0) == 1 + assert galsim.config.GetNObjForFile(config, 0, 0) == [1] + + # Check invalid output type + config['output']['type'] = 'invalid' + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildFile(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.Process(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.GetNImagesForFile(config, 0) + with assert_raises(galsim.GalSimConfigError): + galsim.config.GetNObjForFile(config, 0, 0) + + # If there is no output field, it raises an error when trying to do BuildFile. os.remove('output_fits/test_fits_0.fits') config = galsim.config.CopyConfig(config1) del config['output'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildFile(config) + + # However, when run from a real config file, the processing will write a 'root' field, + # which it will use for the default behavior to write to root.fits. config['root'] = 'output_fits/test_fits_0' galsim.config.BuildFile(config) im2 = galsim.fits.read('output_fits/test_fits_0.fits') @@ -169,6 +190,10 @@ def test_multifits(): im1_list.append(im1) print('multifit image shapes = ',[im.array.shape for im in im1_list]) + assert galsim.config.GetNFiles(config) == 1 + assert galsim.config.GetNImagesForFile(config, 0) == 6 + assert galsim.config.GetNObjForFile(config, 0, 0) == [1, 1, 1, 1, 1, 1] + galsim.config.Process(config) im2_list = galsim.fits.readMulti('output/test_multifits.fits') for k in range(nimages): @@ -237,6 +262,10 @@ def test_datacube(): im1_list.append(im1) print('datacube image shapes = ',[im.array.shape for im in im1_list]) + assert galsim.config.GetNFiles(config) == 1 + assert galsim.config.GetNImagesForFile(config, 0) == 6 + assert galsim.config.GetNObjForFile(config, 0, 0) == [1, 1, 1, 1, 1, 1] + galsim.config.Process(config) im2_list = galsim.fits.readCube('output/test_datacube.fits') for k in range(nimages): From 749054602a734606e88fa437b1140992bde754a9 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 25 Apr 2018 22:05:49 -0400 Subject: [PATCH 39/96] Relax psf flux test slightly. (#755) --- tests/test_config_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_config_output.py b/tests/test_config_output.py index 1bacc013d0e..e3784f8b7a2 100644 --- a/tests/test_config_output.py +++ b/tests/test_config_output.py @@ -828,7 +828,7 @@ def test_extra_psf_sn(): # First pure psf image with no noise. gal_image = galsim.config.BuildImage(config) pure_psf_image = galsim.config.extra.GetFinalExtraOutput('psf', config)[0] - np.testing.assert_almost_equal(pure_psf_image.array.sum(), 1.) + np.testing.assert_almost_equal(pure_psf_image.array.sum(), 1., decimal=6) # Draw PSF at S/N = 100 # (But first check that an error is raised if noise is missing. From 782588c493d6ff22906ae734af97dd7aa56935c5 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 26 Apr 2018 00:35:05 -0400 Subject: [PATCH 40/96] Add a few tests for config that I missed --- galsim/config/extra.py | 4 ++-- tests/test_config_image.py | 2 +- tests/test_config_value.py | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 5510403b1bb..40e3ea433c6 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -211,7 +211,7 @@ def WriteExtraOutputs(config, main_data, logger=None): if 'file_name' in field: galsim.config.SetDefaultExt(field, '.fits') file_name = galsim.config.ParseValue(field,'file_name',config,str)[0] - else: + else: # pragma: no cover (it is convered, but codecov wrongly thinks it isn't. # If no file_name, then probably writing to hdu continue if 'dir' in field: @@ -271,7 +271,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): field = output[key] if 'hdu' in field: hdu = galsim.config.ParseValue(field,'hdu',config,int)[0] - else: + else: # pragma: no cover (it is convered, but codecov wrongly thinks it isn't. # If no hdu, then probably writing to file continue if hdu <= 0 or hdu in hdus: diff --git a/tests/test_config_image.py b/tests/test_config_image.py index dcdd2f913fd..1b65e083bd8 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -640,10 +640,10 @@ def test_ring(): # Invalid to just have a psf. config['psf'] = config['gal'] del config['gal'] + config['stamp']['index'] = 0 with assert_raises(galsim.GalSimConfigError): galsim.config.BuildStamp(config) - config = { 'stamp' : { 'type' : 'Ring', diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 2bded8b8fc3..2e6b9d113e1 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -1558,6 +1558,13 @@ def test_eval(): galsim.config.ParseValue(config,'bad4',config, float) with assert_raises(galsim.GalSimConfigError): galsim.config.ParseValue(config,'bad5',config, float) + config['eval_variables'] = 'itwo' + config = galsim.config.CleanConfig(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'eval3',config, float) + del config['eval_variables'] + with assert_raises(galsim.GalSimConfigError): + galsim.config.ParseValue(config,'eval3',config, float) # Test the evaluation in RandomDistribution # Example config taken directly from Issue #776: From 3e67415076605991038ffa6b52c07455e768da38 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 26 Apr 2018 01:34:26 -0400 Subject: [PATCH 41/96] Increase test coverage of des module (#755) --- galsim/des/des_meds.py | 11 +++-- galsim/des/des_shapelet.py | 72 ++++--------------------------- tests/test_des.py | 86 +++++++++++++++++++++++++++++++------- 3 files changed, 85 insertions(+), 84 deletions(-) diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index 72d10ce68f1..a1f9f2e42a8 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -269,7 +269,8 @@ def WriteMEDS(obj_list, file_name, clobber=True): vec['image'].append(obj.images[i].array.flatten()) vec['seg'].append(obj.seg[i].array.flatten()) vec['weight'].append(obj.weight[i].array.flatten()) - vec['psf'].append(obj.psf[i].array.flatten()) + if obj.psf is not None: + vec['psf'].append(obj.psf[i].array.flatten()) # append cutout_row/col cutout_row[i] = obj.cutout_row[i] @@ -456,11 +457,9 @@ def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger import time t1 = time.time() - if 'image' in base and 'type' in base['image']: - image_type = base['image']['type'] - if image_type != 'Single': - raise galsim.GalSimConfigError( - "MEDS files are not compatible with image type %s."%image_type) + if base.get('image',{}).get('type', 'Single') != 'Single': + raise galsim.GalSimConfigError( + "MEDS files are not compatible with image type %s."%base['image']['type']) req = { 'nobjects' : int , 'nstamps_per_object' : int } ignore += [ 'file_name', 'dir', 'nfiles' ] diff --git a/galsim/des/des_shapelet.py b/galsim/des/des_shapelet.py index 499c690308f..44a00bdbf3e 100644 --- a/galsim/des/des_shapelet.py +++ b/galsim/des/des_shapelet.py @@ -29,12 +29,11 @@ class DES_Shapelet(object): """Class that handles DES files describing interpolated polar shapelet decompositions. - These are usually stored as *_fitpsf.fits files, although there is also an ASCII - version stored as *_fitpsf.dat. + These are stored as *_fitpsf.fits files. They are not used in DES anymore, so this + class is at best of historical interest - The shapelet PSFs are built as part of the WL portion of the DES pipeline. They measure - a shapelet decomposition of each star and interpolate the shapelet coefficients over the - image positions. + The shapelet PSFs measure a shapelet decomposition of each star and interpolate the shapelet + coefficients over the image positions. Unlike PSFEx, these PSF models are built directly in world coordinates. The shapelets know about the WCS, so they are able to fit the shapelet model directly in terms of arcsec. @@ -65,76 +64,21 @@ class DES_Shapelet(object): @param file_name The name of the file to be read in. @param dir Optionally a directory name can be provided if the file names do not already include it. [default: None] - @param file_type Either 'ASCII' or 'FITS' or None. If None, infer from the file name - ending. [default: None] """ _req_params = { 'file_name' : str } - _opt_params = { 'dir' : str, 'file_type' : str } + _opt_params = { 'dir' : str } _single_params = [] _takes_rng = False - def __init__(self, file_name, dir=None, file_type=None): + def __init__(self, file_name, dir=None): if dir: import os file_name = os.path.join(dir,file_name) self.file_name = file_name - - if not file_type: # pragma: no branch - if self.file_name.lower().endswith('.fits'): - file_type = 'FITS' - else: # pragma: no cover - file_type = 'ASCII' - file_type = file_type.upper() - if file_type not in ('FITS', 'ASCII'): - raise galsim.GalSimValueError("Invalid file_type.", file_type, ('FITS', 'ASCII')) - - if file_type == 'FITS': - self.read_fits() - else: # pragma: no cover - self.read_ascii() - - # We haven't used these for a long time, so this is at best of historical interest... - def read_ascii(self): # pragma: no cover - """Read in a DES_Shapelet stored using the the ASCII-file version. - """ - fin = open(self.file_name, 'r') - lines = fin.readlines() - temp = lines[0].split() - self.psf_order = int(temp[0]) - self.psf_size = (self.psf_order+1) * (self.psf_order+2) // 2 - self.sigma = float(temp[1]) - self.fit_order = int(temp[2]) - self.fit_size = (self.fit_order+1) * (self.fit_order+2) // 2 - self.npca = int(temp[3]) - - temp = lines[1].split() - self.bounds = galsim.BoundsD( - float(temp[0]), float(temp[1]), - float(temp[2]), float(temp[3])) - - temp = lines[2].split() - assert int(temp[0]) == self.psf_size - self.ave_psf = np.array(temp[2:self.psf_size+2]).astype(float) - assert self.ave_psf.shape == (self.psf_size,) - - temp = lines[3].split() - assert int(temp[0]) == self.npca - assert int(temp[1]) == self.psf_size - self.rot_matrix = np.array( - [ lines[4+k].split()[1:self.psf_size+1] for k in range(self.npca) ] - ).astype(float) - assert self.rot_matrix.shape == (self.npca, self.psf_size) - - temp = lines[5+self.npca].split() - assert int(temp[0]) == self.fit_size - assert int(temp[1]) == self.npca - self.interp_matrix = np.array( - [ lines[6+self.npca+k].split()[1:self.npca+1] for k in range(self.fit_size) ] - ).astype(float) - assert self.interp_matrix.shape == (self.fit_size, self.npca) + self.read_fits() def read_fits(self): - """Read in a DES_Shapelet stored using the the FITS-file version. + """Read in a DES_Shapelet stored in FITS file. """ from galsim._pyfits import pyfits with pyfits.open(self.file_name) as fits: diff --git a/tests/test_des.py b/tests/test_des.py index 71c463d9f0d..d6cbe88e212 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -57,10 +57,9 @@ def test_meds(): psf13 = galsim.Image(box_size, box_size, init_value=143) dudx = 11.1; dudy = 11.2; dvdx = 11.3; dvdy = 11.4; x0 = 11.5; y0 = 11.6; wcs11 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) - dudx = 12.1; dudy = 12.2; dvdx = 12.3; dvdy = 12.4; x0 = 12.5; y0 = 12.6; - wcs12 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) - dudx = 13.1; dudy = 13.2; dvdx = 13.3; dvdy = 13.4; x0 = 13.5; y0 = 13.6; - wcs13 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) + dudx = 12.1; dudy = 12.2; dvdx = 12.3; dvdy = 12.4; + wcs12 = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) + wcs13 = galsim.PixelScale(13) # create lists @@ -90,10 +89,9 @@ def test_meds(): dudx = 21.1; dudy = 21.2; dvdx = 21.3; dvdy = 21.4; x0 = 21.5; y0 = 21.6; wcs21 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) - dudx = 22.1; dudy = 22.2; dvdx = 22.3; dvdy = 22.4; x0 = 22.5; y0 = 22.6; - wcs22 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) - dudx = 23.1; dudy = 23.2; dvdx = 23.3; dvdy = 23.4; x0 = 23.5; y0 = 23.6; - wcs23 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) + dudx = 22.1; dudy = 22.2; dvdx = 22.3; dvdy = 22.4; + wcs22 = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) + wcs23 = galsim.PixelScale(23) # create lists images = [img21, img22, img23] @@ -109,6 +107,8 @@ def test_meds(): img23.wcs = wcs23 obj2 = galsim.des.MultiExposureObject(images=images, weight=weight, seg=seg, psf=psf, id=2) + obj3 = galsim.des.MultiExposureObject(images=images, id=3) + # create an object list objlist = [obj1, obj2] @@ -116,6 +116,39 @@ def test_meds(): filename_meds = 'output/test_meds.fits' galsim.des.WriteMEDS(objlist, filename_meds, clobber=True) + bad1 = galsim.Image(32, 48, init_value=0) + bad2 = galsim.Image(35, 35, init_value=0) + bad3 = galsim.Image(48, 48, init_value=0) + + with assert_raises(TypeError): + galsim.des.MultiExposureObject(images=img11) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[bad1]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[bad2]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11,bad3]) + with assert_raises(TypeError): + galsim.des.MultiExposureObject(images=images, weight=wth11) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=images, weight=[]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11], weight=[bad3]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11], psf=[bad1]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11], psf=[bad2]) + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11, img12], psf=[bad2, psf12]) + with assert_raises(TypeError): + galsim.des.MultiExposureObject(images=images, wcs=wcs11) + celestial_wcs = galsim.FitsWCS("DECam_00154912_12_header.fits", dir='des_data') + with assert_raises(galsim.GalSimValueError): + galsim.des.MultiExposureObject(images=[img11], wcs=[celestial_wcs]) + + # Note that while there are no tests prior to this, the above still checks for # syntax errors in the meds creation software, so it's still worth running as part # of the normal unit tests. @@ -123,14 +156,10 @@ def test_meds(): # stays in sync with any changes there. try: import meds - except ImportError: - print('Failed to import meds. Unable to do tests of meds file.') - return - try: # Meds will import this, so check for this too. import fitsio except ImportError: - print('Failed to import fitsio. Unable to do tests of meds file.') + print('Failed to import either meds or fitsio. Unable to do tests of meds file.') return # Run meds module's validate function @@ -295,7 +324,18 @@ def get_offset(obj_num): config['output']['badpix'] = {} galsim.config.Process(config, logger=logger) + config = galsim.config.CleanConfig(config) + config['image'] = { + 'type' : 'Scattered', + 'nobjects' : 20, + 'pixel_scale' : pixel_scale, + 'size' : stamp_size , + } + with assert_raises(galsim.GalSimConfigError): + galsim.config.Process(config, logger=logger) + # Now repeat, making a separate file for each + config = galsim.config.CleanConfig(config) config['gal']['half_light_radius'] = { 'type' : 'Sequence', 'first' : 0.7, 'step' : 0.1, 'index_key' : 'file_num' } config['output'] = { 'type' : 'Fits', @@ -310,7 +350,7 @@ def get_offset(obj_num): 'nx_tiles' : 1, 'ny_tiles' : n_per_obj, 'pixel_scale' : pixel_scale, - 'offset' : config['image']['offset'], + 'offset' : { 'type' : 'XY' , 'x' : offset_x, 'y' : offset_y }, 'stamp_size' : stamp_size, 'random_seed' : seed } @@ -594,6 +634,11 @@ def test_psf(): numpy.testing.assert_almost_equal(meas.observed_shape.g2/2, ref_shape.g2/2, decimal=2, err_msg="no-wcs PSFEx shape.g2 doesn't match") + with assert_raises(TypeError): + galsim.des.DES_PSFEx(file_name=file, wcs=wcs_file, dir=data_dir) + with assert_raises(galsim.GalSimError): + galsim.des.DES_PSFEx(psfex_file, image_file_name=wcs_file, wcs=wcs_file, dir=data_dir) + # Now the shapelet PSF model. This model is already in sky coordinates, so no wcs_file needed. fitpsf = galsim.des.DES_Shapelet(os.path.join(data_dir,fitpsf_file)) psf = fitpsf.getPSF(image_pos) @@ -609,6 +654,9 @@ def test_psf(): numpy.testing.assert_almost_equal(meas.observed_shape.g2/2, ref_shape.g2/2, decimal=2, err_msg="Shapelet PSF shape.g2 doesn't match") + with assert_raises(galsim.GalSimBoundsError): + fitpsf.getPSF(image_pos = galsim.PositionD(4000, 5000)) + @timer def test_psf_config(): @@ -637,6 +685,7 @@ def test_psf_config(): 'gsparams' : { 'folding_threshold' : 1.e-4 } }, 'psf5' : { 'type' : 'DES_PSFEx', 'image_pos' : galsim.PositionD(789,567), 'flux' : 388, 'gsparams' : { 'folding_threshold' : 1.e-4 } }, + 'bad1' : { 'type' : 'DES_Shapelet', 'image_pos' : galsim.PositionD(5670,789) }, # This would normally be set by the config processing. Set it manually here. 'image_pos' : image_pos, @@ -673,6 +722,15 @@ def test_psf_config(): psf5b = psfex2.getPSF(galsim.PositionD(789,567),gsparams=gsparams).withFlux(388) gsobject_compare(psf5a, psf5b) + del config['image_pos'] + galsim.config.RemoveCurrent(config) + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'psf1')[0] + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildGSObject(config, 'psf2')[0] + with assert_raises(galsim.config.gsobject.SkipThisObject): + galsim.config.BuildGSObject(config, 'bad1')[0] + if __name__ == "__main__": test_meds() From 0224e5a195cf394367840f31ca1cd09b70b9cd1f Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 26 Apr 2018 20:29:18 -0400 Subject: [PATCH 42/96] Don't support pre-astropy pyfits anymore (#755) --- CHANGELOG.md | 2 + SConstruct | 18 ++--- galsim/_pyfits.py | 15 +--- galsim/catalog.py | 14 +--- galsim/fits.py | 159 +++++---------------------------------- galsim/photon_array.py | 7 +- tests/test_fitsheader.py | 13 +--- tests/test_phase_psf.py | 3 - 8 files changed, 35 insertions(+), 196 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa9d1b3c37..1e3512d896d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ either pip or setup.py. Dependency Changes ------------------ +- No longer support pre-astropy versions of pyfits (now bundled in astropy + as astropy.io.fits). (#755) - Added LSSTDESC.Coord, which contains the functionality that used to be in GalSim as the Angle and CelestialCoord classes. We moved it to a separate repo so people could more easily use this functionality without requiring all diff --git a/SConstruct b/SConstruct index e58a0d0daef..be8c5c5fe9d 100644 --- a/SConstruct +++ b/SConstruct @@ -1763,24 +1763,16 @@ PyMODINIT_FUNC initcheck_numpy(void) return 1 def CheckPyFITS(config): - config.Message('Checking for PyFITS... ') + config.Message('Checking for astropy.io.fits... ') - result, output = TryScript(config,"import pyfits",python) - astropy = False + result, output = TryScript(config,"import astropy.io.fits",python) if not result: - result, output = TryScript(config,"import astropy.io.fits",python) - astropy = True - if not result: - ErrorExit("Unable to import pyfits or astropy.io.fits using the python executable:\n" + + ErrorExit("Unable to import astropy.io.fits using the python executable:\n" + python) config.Result(1) - if astropy: - result, astropy_ver = TryScript(config,"import astropy; print(astropy.__version__)",python) - print('Astropy version is',astropy_ver) - else: - result, pyfits_ver = TryScript(config,"import pyfits; print(pyfits.__version__)",python) - print('PyFITS version is',pyfits_ver) + result, astropy_ver = TryScript(config,"import astropy; print(astropy.__version__)",python) + print('Astropy version is',astropy_ver) return 1 diff --git a/galsim/_pyfits.py b/galsim/_pyfits.py index 2c73d9fd313..c67e8921bd5 100644 --- a/galsim/_pyfits.py +++ b/galsim/_pyfits.py @@ -16,18 +16,9 @@ # and/or other materials provided with the distribution. # -# Make it so we can use either pyfits or astropy.io.fits as pyfits. +# We used to support legacy pyfits in addition to astropy.io.fits. We still call +# astropy.io.fits in the code, but we have removed the legacy compatibility hacks. -try: - import astropy.io.fits as pyfits - # astropy started their versioning over at 0. (Understandably.) - # To make this seamless with pyfits versions, we add 4 to the astropy version. - from astropy import version as astropy_version - pyfits_version = str( (4 + astropy_version.major) + astropy_version.minor/10.) - pyfits_str = 'astropy.io.fits' -except ImportError: - import pyfits - pyfits_version = pyfits.__version__ - pyfits_str = 'pyfits' +import astropy.io.fits as pyfits diff --git a/galsim/catalog.py b/galsim/catalog.py index a0a15577b64..c20c10f90a7 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -134,13 +134,10 @@ def readAscii(self, comments, _nobjects_only=False): def readFits(self, hdu, _nobjects_only=False): """Read in an input catalog from a FITS file. """ - from ._pyfits import pyfits, pyfits_version + from ._pyfits import pyfits with pyfits.open(self.file_name) as fits: raw_data = fits[hdu].data - if pyfits_version > '3.0': - self.names = raw_data.columns.names - else: - self.names = raw_data.dtype.names + self.names = raw_data.columns.names self.nobjects = len(raw_data.field(self.names[0])) if (_nobjects_only): return # The pyfits raw_data is a FITS_rec object, which isn't picklable, so we need to @@ -579,12 +576,7 @@ def writeFitsHdu(self): cols.append(pyfits.Column(name=name, format='%dA'%dt.itemsize, array=data[name])) cols = pyfits.ColDefs(cols) - - # Depending on the version of pyfits, one of these should work: - try: - tbhdu = pyfits.BinTableHDU.from_columns(cols) - except AttributeError: # pragma: no cover - tbhdu = pyfits.new_table(cols) + tbhdu = pyfits.BinTableHDU.from_columns(cols) return tbhdu def __repr__(self): diff --git a/galsim/fits.py b/galsim/fits.py index aad437c4e6d..bb8eef05390 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -39,7 +39,6 @@ ############################################################################################## def _parse_compression(compression, file_name): - from ._pyfits import pyfits, pyfits_version file_compress = None pyfits_compress = None if compression == 'rice' or compression == 'RICE_1': pyfits_compress = 'RICE_1' @@ -59,12 +58,6 @@ def _parse_compression(compression, file_name): raise GalSimValueError("Invalid compression", compression, ('rice', 'gzip_tile', 'hcompress', 'plio', 'gzip', 'bzip2', 'none', 'auto')) - if pyfits_compress: - if 'CompImageHDU' not in pyfits.__dict__: - raise NotImplementedError( - 'Compressed Images not supported before pyfits version 2.0. You have version %s.'%( - pyfits_version)) - return file_compress, pyfits_compress # This is a class rather than a def, since we want to store some variable, and that's easier @@ -186,26 +179,13 @@ def __init__(self): self.bz2 = self.bz2_methods[0] def __call__(self, file, dir, file_compress): - from ._pyfits import pyfits, pyfits_version + from ._pyfits import pyfits if dir: import os file = os.path.join(dir,file) if not file_compress: - if pyfits_version < '3.1': # pragma: no cover - # Sometimes early versions of pyfits do weird things with the final hdu when - # writing fits files with rice compression. It seems to add a bunch of '\0' - # characters after the end of what should be the last hdu. When reading this - # back in, it gets interpreted as the start of another hdu, which is then found - # to be missing its END card in the header. The easiest workaround is to just - # tell it to ignore any missing END problems on the read command. Also ignore - # the warnings it emits along the way. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - hdu_list = pyfits.open(file, 'readonly', ignore_missing_end=True) - else: - hdu_list = pyfits.open(file, 'readonly') + hdu_list = pyfits.open(file, 'readonly') return hdu_list, None elif file_compress == 'gzip': # Before trying all the gzip options, first make sure the file exists and is readable. @@ -290,8 +270,8 @@ def gzip_in_mem(self, hdu_list, file): # pragma: no cover def gzip_tmp(self, hdu_list, file): # pragma: no cover import gzip - # However, pyfits versions before 2.3 do not support writing to a buffer, so the - # above code will fail. We need to use a temporary in that case. + # Old pyfits versions did not support writing to a buffer, so the # above code will fail. + # This is probably not necessary anymore, but left here as another option in case useful. tmp = file + '.tmp' # It would be pretty odd for this filename to already exist, but just in case... while os.path.isfile(tmp): @@ -378,7 +358,6 @@ def __init__(self): def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress): import os - from ._pyfits import pyfits, pyfits_version if dir: file = os.path.join(dir,file) @@ -415,42 +394,14 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) else: raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) - # There is a bug in pyfits where they don't add the size of the variable length array - # to the TFORMx header keywords. They should have size at the end of them. - # This bug has been fixed in version 3.1.2. - # (See http://trac.assembla.com/pyfits/ticket/199) - if pyfits_compress and pyfits_version < '3.1.2': - with pyfits.open(file,'update',disable_image_compression=True) as hdu_list: - for hdu in hdu_list[1:]: # Skip PrimaryHDU - # Find the maximum variable array length - max_ar_len = max([ len(ar[0]) for ar in hdu.data ]) - # Add '(N)' to the TFORMx keywords for the variable array items - s = '(%d)'%max_ar_len - for key in hdu.header.keys(): - if key.startswith('TFORM'): - tform = hdu.header[key] - # Only update if the form is a P (= variable length data) - # and the (*) is not there already. - if 'P' in tform and '(' not in tform: - hdu.header[key] = tform + s - - # Workaround for a bug in some pyfits 3.0.x versions - # It was fixed in 3.0.8. I'm not sure when the bug was - # introduced, but I believe it was 3.0.3. - if (pyfits_version > '3.0' and pyfits_version < '3.0.8' and - 'COMPRESSION_ENABLED' in pyfits.hdu.compressed.__dict__): - pyfits.hdu.compressed.COMPRESSION_ENABLED = True _write_file = _WriteFile() def _add_hdu(hdu_list, data, pyfits_compress): - from ._pyfits import pyfits, pyfits_version + from ._pyfits import pyfits if pyfits_compress: if len(hdu_list) == 0: hdu_list.append(pyfits.PrimaryHDU()) # Need a blank PrimaryHDU - if pyfits_version < '4.3': - hdu = pyfits.CompImageHDU(data, compressionType=pyfits_compress) - else: - hdu = pyfits.CompImageHDU(data, compression_type=pyfits_compress) + hdu = pyfits.CompImageHDU(data, compression_type=pyfits_compress) else: if len(hdu_list) == 0: hdu = pyfits.PrimaryHDU(data) @@ -1274,11 +1225,7 @@ def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=Non # The rest of the functions are typical non-mutating functions for a dict, for which we # generally just pass the request along to self.header. def __len__(self): - from ._pyfits import pyfits_version - if pyfits_version < '3.1': - return len(self.header.ascard) - else: - return len(self.header) + return len(self.header) def __contains__(self, key): return key in self.header @@ -1308,35 +1255,13 @@ def __setitem__(self, key, value): value = str(value.decode()) except AttributeError: pass - from ._pyfits import pyfits_version self._tag = None - if pyfits_version < '3.1': - if isinstance(value, tuple): - # header[key] = (value, comment) syntax - if not (0 < len(value) <= 2): - raise GalSimValueError( - 'A Header item may be set with either a scalar value, ' - 'a 1-tuple containing a scalar value, or a 2-tuple ' - 'containing a scalar value and comment string.', value) - elif len(value) == 1: - self.header.update(key, value[0]) - else: - self.header.update(key, value[0], value[1]) - else: - # header[key] = value syntax - self.header.update(key, value) - else: - # Recent versions implement the above logic with the regular setitem method. - self.header[key] = value + # Recent versions implement the above logic with the regular setitem method. + self.header[key] = value def clear(self): - from ._pyfits import pyfits_version self._tag = None - if pyfits_version < '3.1': - # Not sure when clear() was added, but not present in 2.4, and present in 3.1. - del self.header.ascardlist()[:] - else: - self.header.clear() + self.header.clear() def get(self, key, default=None): return self.header.get(key, default) @@ -1345,44 +1270,24 @@ def items(self): return self.header.items() def iteritems(self): - from ._pyfits import pyfits_version - if pyfits_version < '3.1': - return self.header.items() - else: - return iteritems(self.header) + return iteritems(self.header) def iterkeys(self): - from ._pyfits import pyfits_version - if pyfits_version < '3.1': - return self.header.keys() - else: - return iterkeys(self.header) + return iterkeys(self.header) def itervalues(self): - from ._pyfits import pyfits_version - if pyfits_version < '3.1': - return self.header.ascard.values() - else: - return itervalues(self.header) + return itervalues(self.header) def keys(self): return self.header.keys() def update(self, dict2): - from ._pyfits import pyfits_version self._tag = None - # dict2 may be a dict or another FitsHeader (or anything that acts like a dict). - # Note: Don't use self.header.update, since that sometimes has problems (in astropy) - # with COMMENT lines. The __setitem__ syntax seems to work properly though. - for k, v in iteritems(dict2): - self[k] = v + for key, item in dict2.items(): + self.header[key] = item def values(self): - from ._pyfits import pyfits_version - if pyfits_version < '3.1': - return self.header.ascard.values() - else: - return self.header.values() + return self.header.values() def append(self, key, value='', useblanks=True): """Append an item to the end of the header. @@ -1395,19 +1300,10 @@ def append(self, key, value='', useblanks=True): @param useblanks If there are blank entries currently at the end, should they be overwritten with the new entry? [default: True] """ - from ._pyfits import pyfits, pyfits_version self._tag = None - if pyfits_version < '3.1': - # NB. append doesn't quite do what it claims when useblanks=False. - # If there are blanks, it doesn't put the new item after the blanks. - # Inserting before the end does do what we want. - self.header.ascardlist().insert(len(self), pyfits.Card(key, value), - useblanks=useblanks) - else: - self.header.insert(len(self), (key, value), useblanks=useblanks) + self.header.insert(len(self), (key, value), useblanks=useblanks) def __repr__(self): - from ._pyfits import pyfits_str if self._tag is None: return "galsim.FitsHeader(header=%r)"%list(self.items()) else: @@ -1428,26 +1324,5 @@ def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(tuple(sorted(self.items()))) - def __deepcopy__(self, memo): - # Need this because pyfits.Header deepcopy was broken before 3.0.6. - # cf. https://aeon.stsci.edu/ssb/trac/pyfits/ticket/115 - from ._pyfits import pyfits, pyfits_version - import copy - # Boilerplate deepcopy implementation. - # cf. http://stackoverflow.com/questions/1500718/what-is-the-right-way-to-override-the-copy-deepcopy-operations-on-an-object-in-p - cls = self.__class__ - result = cls.__new__(cls) - memo[id(self)] = result - d1 = self.__dict__ - # This is the special bit for this case. - if pyfits_version < '3.0.6': - # Not technically a deepcopy apparently, but good enough in most cases. - result.header = self.header.copy() - d1 = d1.copy() - del d1['header'] - for k, v in d1.items(): - setattr(result, k, copy.deepcopy(v, memo)) - return result - # inject write as method of Image class Image.write = write diff --git a/galsim/photon_array.py b/galsim/photon_array.py index 2d844ac6d2a..96e30523670 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -371,14 +371,11 @@ def read(cls, file_name): @param file_name The file name of the input FITS file. """ - from ._pyfits import pyfits, pyfits_version + from ._pyfits import pyfits with pyfits.open(file_name) as fits: data = fits[1].data N = len(data) - if pyfits_version > '3.0': - names = data.columns.names - else: # pragma: no cover - names = data.dtype.names + names = data.columns.names photons = cls(N, x=data['x'], y=data['y'], flux=data['flux']) if 'dxdz' in names: diff --git a/tests/test_fitsheader.py b/tests/test_fitsheader.py index cb95ff2a3ea..44ee57efa8c 100644 --- a/tests/test_fitsheader.py +++ b/tests/test_fitsheader.py @@ -26,16 +26,13 @@ # Get whatever version of pyfits or astropy we are using -from galsim._pyfits import pyfits, pyfits_version +from galsim._pyfits import pyfits @timer def test_read(): """Test reading a FitsHeader from an existing FITS file """ - # Older pyfits versions treat the blank rows differently, so it comes out as 213. - # I don't know exactly when it switched, but for < 3.1, I'll just update this to - # whatever the initial value is. tpv_len = 215 def check_tpv(header): @@ -58,8 +55,6 @@ def check_tpv(header): dir = 'fits_files' # First option: give a file_name header = galsim.FitsHeader(file_name=os.path.join(dir,file_name)) - if pyfits_version < '3.1': - tpv_len = len(header) check_tpv(header) do_pickle(header) # Let the FitsHeader init handle the dir @@ -107,8 +102,7 @@ def check_tpv(header): assert header == orig_header del header['AIRMASS'] assert 'AIRMASS' not in header - if pyfits_version >= '3.1': - assert len(header) == tpv_len-1 + assert len(header) == tpv_len-1 assert header != orig_header do_pickle(header) @@ -116,8 +110,7 @@ def check_tpv(header): assert header.get('AIRMASS', 2.0) == 2.0 # key should still not be in the header assert 'AIRMASS' not in header - if pyfits_version >= '3.1': - assert len(header) == tpv_len-1 + assert len(header) == tpv_len-1 assert header != orig_header # Add items to a header diff --git a/tests/test_phase_psf.py b/tests/test_phase_psf.py index 7d8f021feb4..b3675535d4f 100644 --- a/tests/test_phase_psf.py +++ b/tests/test_phase_psf.py @@ -802,9 +802,6 @@ def test_speedup(): significant speedup. """ import time - #import cProfile, pstats - #pr = cProfile.Profile() - #pr.enable() atm = galsim.Atmosphere(screen_size=10.0, altitude=[0,1,2,3], r0_500=0.2) # Should be ~seconds if _prepareDraw() gets executed, ~0.01s otherwise. psf = atm.makePSF(lam=500.0, diam=1.0, exptime=15.0, time_step=0.025) From b569fef008ec1e692a9859428db24e35ae925f86 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 27 Apr 2018 01:10:11 -0400 Subject: [PATCH 43/96] Increase test coverage of wfirst module (#755) --- CHANGELOG.md | 1 + galsim/detectors.py | 16 +- galsim/fits.py | 5 +- galsim/utilities.py | 41 ++-- galsim/wfirst/wfirst_backgrounds.py | 9 +- galsim/wfirst/wfirst_bandpass.py | 2 +- galsim/wfirst/wfirst_detectors.py | 47 ++-- galsim/wfirst/wfirst_psfs.py | 46 ++-- galsim/wfirst/wfirst_wcs.py | 151 ++++++------- tests/.coveragerc | 1 + tests/test_detectors.py | 14 +- tests/test_utilities.py | 78 +++++-- tests/test_wfirst.py | 332 +++++++++++++++++++--------- 13 files changed, 447 insertions(+), 296 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3512d896d..52be8bbcd2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Dependency Changes API Changes ----------- +- Changed the order of arguments of galsim.wfirst.allDetectorEffects. (#755) - Most of the functionality associated with C++-layer objects has been redesigned or removed. These were non-public-API features, so if you have been using the public API, you should be fine. But if you have been relying diff --git a/galsim/detectors.py b/galsim/detectors.py index 6d7174e8d8b..a58143e937c 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -214,17 +214,15 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ raise GalSimValueError("IPC kernel must be an Image instance of size 3x3.", IPC_kernel) # Check for non-negativity of the kernel - if kernel_nonnegativity is True: - if (ipc_kernel<0).any() is True: - raise GalSimValueError("IPC kernel must not contain negative entries", IPC_kernel) + if kernel_nonnegativity and (ipc_kernel<0).any(): + raise GalSimValueError("IPC kernel must not contain negative entries", IPC_kernel) # Check and enforce correct normalization for the kernel - if kernel_normalization is True: - if abs(ipc_kernel.sum() - 1.0) > 10.*np.finfo(ipc_kernel.dtype.type).eps: - import warnings - warnings.warn("The entries in the IPC kernel did not sum to 1. Scaling the kernel to "\ - +"ensure correct normalization.", GalSimWarning) - IPC_kernel = IPC_kernel/ipc_kernel.sum() + if kernel_normalization and abs(ipc_kernel.sum()-1) > 10.*np.finfo(ipc_kernel.dtype.type).eps: + import warnings + warnings.warn("The entries in the IPC kernel did not sum to 1. Scaling the kernel to "\ + +"ensure correct normalization.", GalSimWarning) + IPC_kernel = IPC_kernel/ipc_kernel.sum() # edge_treatment can be 'extend', 'wrap' or 'crop' if edge_treatment=='crop': diff --git a/galsim/fits.py b/galsim/fits.py index bb8eef05390..ddb846674fa 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -1218,9 +1218,8 @@ def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=Non # update() should handle anything that acts like a dict. self.update(header) else: - # for a list, just add each item one at a time. - for k,v in header: - self.append(k,v,useblanks=False) + for card in header: + self.header.append(card, end=True) # The rest of the functions are typical non-mutating functions for a dict, for which we # generally just pass the request along to self.header. diff --git a/galsim/utilities.py b/galsim/utilities.py index 7e9144af6b3..7b0818ddd5a 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -669,17 +669,11 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): from .wcs import JacobianWCS, PixelScale if isinstance(N,int): n1,n2 = N,N - elif hasattr(N,'__iter__'): - if len(N)==2: - n1,n2 = N - else: - raise TypeError("N must be a list or a tuple of two integers") - if not (n1 == int(n1) and n2 == int(n2)): - raise TypeError("N must be of type int or a list or a tuple of two integers") - n1 = int(n1) - n2 = int(n2) else: - raise TypeError("N must be of type int or a list or a tuple of two integers") + try: + n1,n2 = N + except (TypeError, ValueError): + raise TypeError("N must be an integer or a tuple of two integers") if not isinstance(image, Image): raise TypeError("image must be an instance of galsim.Image") @@ -729,7 +723,7 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False, - catch_offset_errors=True): + catch_offset_errors=True): """ Interleaves the pixel values from two or more images and into a single larger image. @@ -807,20 +801,14 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False from .wcs import PixelScale, JacobianWCS if isinstance(N,int): n1,n2 = N,N - elif hasattr(N,'__iter__'): - if len(N)==2: - n1,n2 = N - else: - raise TypeError("N must be a list or a tuple of two integers") - if not (n1 == int(n1) and n2 == int(n2)): - raise TypeError("N must be of type int or a list or a tuple of two integers") - n1 = int(n1) - n2 = int(n2) else: - raise TypeError("N must be of type int or a list or a tuple of two integers") + try: + n1,n2 = N + except (TypeError, ValueError): + raise TypeError("N must be an integer or a tuple of two integers") if len(im_list)<2: - raise TypeError("im_list must have at least two instances of galsim.Image") + raise GalSimValueError("im_list must have at least two instances of galsim.Image", im_list) if (n1*n2 != len(im_list)): raise GalSimIncompatibleValuesError( @@ -873,13 +861,18 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), N=N, offsets=offsets) - if i<0 or j<0 or i>=x_size or j>=y_size: + if i<0 or j<0 or i>=n1 or j>=n2: raise GalSimIncompatibleValuesError( "offsets must be a list of galsim.PositionD instances with x values " "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), N=N, offsets=offsets) + else: + # If we're told to just trust the offsets, at least make sure the slice will be + # the right shape. + i = i%n1 + j = j%n2 - img_array[j::n2,i::n1] = im_list[k].array[:,:] + img_array[j::n2,i::n1] = im_list[k].array img = Image(img_array) if not add_flux: diff --git a/galsim/wfirst/wfirst_backgrounds.py b/galsim/wfirst/wfirst_backgrounds.py index fe31d46509e..30741a5712d 100644 --- a/galsim/wfirst/wfirst_backgrounds.py +++ b/galsim/wfirst/wfirst_backgrounds.py @@ -99,12 +99,9 @@ def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): # The table only includes longitude in the range [0, 180] because there is symmetry in that a # negative longitude in the range[-180, 0] should have the same sky level as at the positive # value of longitude (given that the Sun is at 0). - if ecliptic_lon/galsim.degrees > 180.: - ecliptic_lon -= 360.*galsim.degrees - ecliptic_lon = abs(ecliptic_lon/galsim.degrees)*galsim.degrees - # And latitude symmetries: - if ecliptic_lat/galsim.degrees < 0.: - ecliptic_lat = abs(ecliptic_lat/galsim.degrees)*galsim.degrees + ecliptic_lon = ecliptic_lon.wrap() + ecliptic_lon = abs(ecliptic_lon.rad)*galsim.radians + ecliptic_lat = abs(ecliptic_lat.rad)*galsim.radians sin_ecliptic_lat = np.sin(ecliptic_lat) # Take the lookup table, and turn negative numbers (indicating failure because of proximity to diff --git a/galsim/wfirst/wfirst_bandpass.py b/galsim/wfirst/wfirst_bandpass.py index 2ac2b5dbeff..200e3ff1b65 100644 --- a/galsim/wfirst/wfirst_bandpass.py +++ b/galsim/wfirst/wfirst_bandpass.py @@ -110,7 +110,7 @@ def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, **kwargs): ' default_thin_trunc.', galsim.GalSimWarning) default_thin_trunc = False if len(kwargs) > 0: - for key in kwargs: + for key in list(kwargs.keys()): if key in truncate_kwargs: tmp_truncate_dict[key] = kwargs.pop(key) if key in thin_kwargs: diff --git a/galsim/wfirst/wfirst_detectors.py b/galsim/wfirst/wfirst_detectors.py index 2c30418709f..2282de88293 100644 --- a/galsim/wfirst/wfirst_detectors.py +++ b/galsim/wfirst/wfirst_detectors.py @@ -27,6 +27,9 @@ import numpy as np import os +from . import exptime as default_exptime + + def applyNonlinearity(img): """ Applies the WFIRST nonlinearity function to the supplied image `im`. @@ -41,7 +44,7 @@ def applyNonlinearity(img): """ img.applyNonlinearity(NLfunc=galsim.wfirst.NLfunc) -def addReciprocityFailure(img, exptime=None): +def addReciprocityFailure(img, exptime=default_exptime): """ Accounts for the reciprocity failure for the WFIRST directors and includes it in the original Image `img` directly. @@ -55,10 +58,8 @@ def addReciprocityFailure(img, exptime=None): @param exptime The exposure time (t) in seconds, which goes into the expression for reciprocity failure given in the docstring. If None, then the routine will use the default WFIRST exposure time in galsim.wfirst.exptime. - [default: None] - """ - if exptime is None: - exptime=galsim.wfirst.exptime + [default: {exptime}] + """.format(exptime=default_exptime) img.addReciprocityFailure(exp_time=exptime, alpha=galsim.wfirst.reciprocity_alpha, base_flux=1.0) @@ -95,22 +96,15 @@ def applyPersistence(img, prev_exposures): @param img The Image to be transformed. @param prev_exposures List of up to {ncoeff} Image instances in the order of exposures, with the recent exposure being the first element. - """.format(ncoeff=galsim.wfirst.persistence_coefficients) + """.format(ncoeff=len(galsim.wfirst.persistence_coefficients)) if not hasattr(prev_exposures,'__iter__'): raise TypeError("In wfirst.applyPersistence, prev_exposures must be a list of Image " "instances") n_exp = min(len(prev_exposures),len(galsim.wfirst.persistence_coefficients)) - if n_exp > len(galsim.wfirst.persistence_coefficients): - import warnings - warnings.warn("More than {ncoeff} Image instances were passed to" - "galsim.wfirst.applyPersistence routine. Ignoring the earlier" - "exposures".format(ncoeff=galsim.wfirst.persistence_coefficients), - galsim.GalSimWarning) - img.applyPersistence(prev_exposures[:n_exp],galsim.wfirst.persistence_coefficients[:n_exp]) -def allDetectorEffects(img, rng=None, exptime=None, prev_exposures=[]): +def allDetectorEffects(img, prev_exposures=(), rng=None, exptime=default_exptime): """ This utility applies all sources of noise and detector effects for WFIRST that are implemented in GalSim. In terms of noise, this includes the Poisson noise due to the signal (sky + @@ -125,25 +119,22 @@ def allDetectorEffects(img, rng=None, exptime=None, prev_exposures=[]): recent exposures. @param img The Image to be modified. + @param prev_exposures List of up to {ncoeff} Image instances in the order of exposures, with + the recent exposure being the first element. [default: []] @param rng An optional galsim.BaseDeviate to use for the addition of noise. If None, a new one will be initialized. [default: None] @param exptime The exposure time, in seconds. If None, then the WFIRST default - exposure time will be used. [default: None] - @param prev_exposures List of up to {ncoeff} Image instances in the order of exposures, with - the recent exposure being the first element. [default: []] + exposure time will be used. [default: {exptime}] @returns prev_exposures Updated list of previous exposures containing up to {ncoeff} Image instances. - """.format(ncoeff=galsim.wfirst.persistence_coefficients) - # Deal appropriately with passed-in RNG, exposure time. - if rng is None: - rng = galsim.BaseDeviate() - elif not isinstance(rng, galsim.BaseDeviate): - raise TypeError("The rng provided to RealGalaxy constructor is not a BaseDeviate") - if exptime is None: - exptime=galsim.wfirst.exptime + """.format(ncoeff=len(galsim.wfirst.persistence_coefficients), exptime=default_exptime) + + # Make sure we don't have any negative values. + img.replaceNegative(0.) # Add Poisson noise. + rng = galsim.BaseDeviate(rng) poisson_noise = galsim.PoissonNoise(rng) img.addNoise(poisson_noise) @@ -159,12 +150,12 @@ def allDetectorEffects(img, rng=None, exptime=None, prev_exposures=[]): img.addNoise(dark_noise) # Persistence (use WFIRST coefficients) + prev_exposures = list(prev_exposures) applyPersistence(img,prev_exposures) # Update the 'prev_exposures' queue - if len(prev_exposures)>=len(galsim.wfirst.persistence_coefficients): - prev_exposures.pop() - prev_exposures.insert(0,img.copy()) + ncoeff = len(galsim.wfirst.persistence_coefficients) + prev_exposures = [img.copy()] + prev_exposures[:ncoeff-1] # Nonlinearity (use WFIRST routine). applyNonlinearity(img) diff --git a/galsim/wfirst/wfirst_psfs.py b/galsim/wfirst/wfirst_psfs.py index 257cdf06ba3..3d24d8d5ce1 100644 --- a/galsim/wfirst/wfirst_psfs.py +++ b/galsim/wfirst/wfirst_psfs.py @@ -185,19 +185,18 @@ def getPSF(SCAs=None, approximate_struts=False, n_waves=None, extra_aberrations= blue_limit, red_limit = _find_limits(default_bandpass_list, bandpass_dict) else: if not isinstance(wavelength_limits, tuple): - raise TypeError("Wavelength limits must be entered as a tuple!") + raise TypeError("Wavelength limits must be entered as a tuple.") blue_limit, red_limit = wavelength_limits if red_limit <= blue_limit: - raise GalSimIncompatibleValuesError( + raise galsim.GalSimIncompatibleValuesError( "Wavelength limits must have red_limit > blue_limit.", blue_limit=blue_limit, red_limit=red_limit) + elif isinstance(wavelength, float): + wavelength_nm = wavelength + elif isinstance(wavelength, galsim.Bandpass): + wavelength_nm = wavelength.effective_wavelength else: - if isinstance(wavelength, galsim.Bandpass): - wavelength_nm = wavelength.effective_wavelength - elif isinstance(wavelength, float): - wavelength_nm = wavelength - else: - raise TypeError("wavelength should either be a Bandpass, float, or None.") + raise TypeError("wavelength should either be a Bandpass, float, or None.") # Start reading in the aberrations for the relevant SCAs. aberration_dict = {} @@ -279,11 +278,11 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): # Check for sane input PSF_dict. if len(PSF_dict) == 0 or len(PSF_dict) > galsim.wfirst.n_sca or \ min(PSF_dict.keys()) < 1 or max(PSF_dict.keys()) > galsim.wfirst.n_sca: - raise GalSimError("PSF_dict must come from getPSF()!") + raise galsim.GalSimError("PSF_dict must come from getPSF().") # Check if file already exists and warn about clobbering. if os.path.isfile(filename): - if not clobber: + if clobber: os.remove(filename) else: raise OSError("Output file %r already exists"%filename) @@ -294,11 +293,8 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): bandpass_list = default_bandpass_list else: if not isinstance(bandpass_list[0], basestring): - raise TypeError("Expected input list of bandpass names!") + raise TypeError("Expected input list of bandpass names.") if not set(bandpass_list).issubset(default_bandpass_list): - err_msg = '' - for item in default_bandpass_list: - err_msg += item+' ' raise galsim.GalSimValueError("Invalid values in bandpass_list", bandpass_list, default_bandpass_list) @@ -313,7 +309,7 @@ def storePSFImages(PSF_dict, filename, bandpass_list=None, clobber=False): PSF = PSF_dict[SCA] if not isinstance(PSF, galsim.ChromaticOpticalPSF) and \ not isinstance(PSF, galsim.InterpolatedChromaticObject): - raise TypeError("PSFs are not ChromaticOpticalPSFs.") + raise galsim.GalSimValueError("PSFs are not ChromaticOpticalPSFs.", PSF_dict) star = galsim.Gaussian(sigma=1.e-8, flux=1.) for bp_name in bandpass_list: @@ -375,20 +371,19 @@ def loadPSFImages(filename): # Find all indices in `bp_list` that correspond to this bandpass. bp_indices = [] - if band_name in bp_list: - idx = -1 - while True: - try: - idx = bp_list.index(band_name, idx+1) - bp_indices.append(idx) - except ValueError: - break + idx = -1 + while True: + try: + idx = bp_list.index(band_name, idx+1) + bp_indices.append(idx) + except ValueError: + break for SCA in SCA_list: # Now find which element has both the right band_name and is for this SCA. There might # not be any, depending on what exactly was stored. use_idx = -1 - for index in bp_indices: + for index in bp_indices: # pragma: no branch if SCA_list[index] == SCA: use_idx = index break @@ -411,9 +406,6 @@ def _read_aberrations(SCA): @returns a NumPy array containing the aberrations, in the required format for ChromaticOpticalPSF. """ - if SCA < 1 or SCA > galsim.wfirst.n_sca: - raise galsim.GalSimRangeError("Invalid SCA requested", SCA, 1, galsim.wfirst.n_sca) - # Construct filename. sca_str = '%02d'%SCA infile = os.path.join(galsim.meta_data.share_dir, diff --git a/galsim/wfirst/wfirst_wcs.py b/galsim/wfirst/wfirst_wcs.py index 601d2c2f023..170f4e90b70 100644 --- a/galsim/wfirst/wfirst_wcs.py +++ b/galsim/wfirst/wfirst_wcs.py @@ -147,7 +147,7 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): # Parse input position if not isinstance(world_pos, galsim.CelestialCoord): - raise TypeError("Position on the sky must be given as a galsim.CelestialCoord!") + raise TypeError("Position on the sky must be given as a galsim.CelestialCoord.") # Get the date. (Vernal equinox in 2025, taken from # http://www.astropixels.com/ephemeris/soleq2001.html, if none was supplied.) @@ -157,7 +157,7 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): # Are we allowed to look here? if not allowedPos(world_pos, date): - raise galsim.GalSimError("Error, WFIRST cannot look at this position on this date!") + raise galsim.GalSimError("Error, WFIRST cannot look at this position on this date.") # If position angle was not given, then get the optimal one: if PA is None: @@ -166,7 +166,7 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): else: # Just enforce type if not isinstance(PA, galsim.Angle): - raise TypeError("Position angle must be a galsim.Angle!") + raise TypeError("Position angle must be a galsim.Angle.") # Check which SCAs are to be done using a helper routine in this module. SCAs = galsim.wfirst._parse_SCAs(SCAs) @@ -192,22 +192,22 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): wcs_dict = {} for i_sca in SCAs: # Set up the header. - header = galsim.FitsHeader() + header = [] # Populate some necessary variables in the FITS header that are always the same, regardless of # input and SCA number. _populate_required_fields(header) # And populate some things that just depend on the overall locations or other input, not on # the SCA. - header['RA_TARG'] = (world_pos.ra / galsim.degrees, - "right ascension of the target (deg) (J2000)") - header['DEC_TARG'] = (world_pos.dec / galsim.degrees, - "declination of the target (deg) (J2000)") - header['PA_OBSY'] = (pa_obsy / galsim.degrees, "position angle of observatory Y axis (deg)") - header['PA_FPA'] = (pa_fpa / galsim.degrees, "position angle of FPA Y axis (deg)") - - # Finally do all the SCA-specific stuff. - header['SCA_NUM'] = (i_sca, "SCA number (1 - 18)") + header.extend([ + ('RA_TARG', world_pos.ra / galsim.degrees, + "right ascension of the target (deg) (J2000)"), + ('DEC_TARG', world_pos.dec / galsim.degrees, + "declination of the target (deg) (J2000)"), + ('PA_OBSY', pa_obsy / galsim.degrees, "position angle of observatory Y axis (deg)"), + ('PA_FPA', pa_fpa / galsim.degrees, "position angle of FPA Y axis (deg)"), + ('SCA_NUM', i_sca, "SCA number (1 - 18)"), + ]) # Set the position of center of this SCA in focal plane angular coordinates. sca_xc_fpa = np.arctan(sca_xc_mm[i_sca]/focal_length)*galsim.radians @@ -240,8 +240,6 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): crval = world_pos.deproject(u, v, projection='gnomonic') crval1 = crval.ra crval2 = crval.dec - header['CRVAL1'] = (crval1 / galsim.degrees, "first axis value at reference pixel") - header['CRVAL2'] = (crval2 / galsim.degrees, "second axis value at reference pixel") # Compute the position angle of the local pixel Y axis. # This requires projecting local North onto the detector axes. @@ -269,26 +267,30 @@ def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): # Rotate by pa_fpa. cos_pa_sca = np.cos(pa_sca) sin_pa_sca = np.sin(pa_sca) - header['CD1_1'] = (cos_pa_sca * a10 + sin_pa_sca * b10, - "partial of first axis coordinate w.r.t. x") - header['CD1_2'] = (cos_pa_sca * a11 + sin_pa_sca * b11, - "partial of first axis coordinate w.r.t. y") - header['CD2_1'] = (-sin_pa_sca * a10 + cos_pa_sca * b10, - "partial of second axis coordinate w.r.t. x") - header['CD2_2'] = (-sin_pa_sca * a11 + cos_pa_sca * b11, - "partial of second axis coordinate w.r.t. y") - header['ORIENTAT'] = (pa_sca / galsim.degrees, - "position angle of image y axis (deg. e of n)") - header['LONPOLE'] = (phi_p / galsim.degrees, - "Native longitude of celestial pole") + + header.extend([ + ('CRVAL1', crval1 / galsim.degrees, "first axis value at reference pixel"), + ('CRVAL2', crval2 / galsim.degrees, "second axis value at reference pixel"), + ('CD1_1', cos_pa_sca * a10 + sin_pa_sca * b10, + "partial of first axis coordinate w.r.t. x"), + ('CD1_2', cos_pa_sca * a11 + sin_pa_sca * b11, + "partial of first axis coordinate w.r.t. y"), + ('CD2_1', -sin_pa_sca * a10 + cos_pa_sca * b10, + "partial of second axis coordinate w.r.t. x"), + ('CD2_2', -sin_pa_sca * a11 + cos_pa_sca * b11, + "partial of second axis coordinate w.r.t. y"), + ('ORIENTAT', pa_sca / galsim.degrees, "position angle of image y axis (deg. e of n)"), + ('LONPOLE', phi_p / galsim.degrees, "Native longitude of celestial pole"), + ]) for i in range(n_sip): for j in range(n_sip): if i+j >= 2 and i+j < n_sip: sipstr = "A_%d_%d"%(i,j) - header[sipstr] = a_sip[i_sca,i,j] + header.append( (sipstr, a_sip[i_sca,i,j]) ) sipstr = "B_%d_%d"%(i,j) - header[sipstr] = b_sip[i_sca,i,j] + header.append( (sipstr, b_sip[i_sca,i,j]) ) + header = galsim.FitsHeader(header) wcs = galsim.GSFitsWCS(header=header) # Store the original header as an attribute of the WCS. This ensures that we have all the # extra keywords for whenever an image with this WCS is written to file. @@ -324,11 +326,10 @@ def findSCA(wcs_dict, world_pos, include_border=False): """ # Sanity check args. if not isinstance(wcs_dict, dict): - raise TypeError("wcs_dict should be a dict containing WCS output by " - "galsim.wfirst.getWCS!"%galsim.wfirst.n_sca) + raise TypeError("wcs_dict should be a dict containing WCS output by galsim.wfirst.getWCS.") if not isinstance(world_pos, galsim.CelestialCoord): - raise TypeError("Position on the sky must be given as a galsim.CelestialCoord!") + raise TypeError("Position on the sky must be given as a galsim.CelestialCoord.") # Set up the minimum and maximum pixel values, depending on whether or not to include the # border. We put it immediately into a galsim.BoundsI(), since the routine returns xmin, xmax, @@ -340,7 +341,7 @@ def findSCA(wcs_dict, world_pos, include_border=False): for i_sca in wcs_dict: wcs = wcs_dict[i_sca] image_pos = wcs.toImage(world_pos) - if bounds_list[i_sca-1].includes(int(image_pos.x), int(image_pos.y)): + if bounds_list[i_sca].includes(image_pos): sca = i_sca break @@ -370,28 +371,28 @@ def _calculate_minmax_pix(include_border=False): # are the same, but that won't always be the case, so for the sake of generality we only # group together those that are forced to be the same. # - # Negative side of 1/2/3, same as positive side of 10/11/12 + # Positive side of 1/2/3, same as negative side of 10/11/12 border_mm = abs(sca_xc_mm[1]-sca_xc_mm[10])-galsim.wfirst.n_pix*pixel_size_mm half_border_pix = int(0.5*border_mm / pixel_size_mm) - min_x_pix[1:4] -= half_border_pix - max_x_pix[10:13] += half_border_pix + max_x_pix[1:4] += half_border_pix + min_x_pix[10:13] -= half_border_pix - # Positive side of 1/2/3 and 13/14/15, same as negative side of 10/11/12, 4/5/6 + # Negative side of 1/2/3 and 13/14/15, same as positive side of 10/11/12, 4/5/6 border_mm = abs(sca_xc_mm[1]-sca_xc_mm[4])-galsim.wfirst.n_pix*pixel_size_mm half_border_pix = int(0.5*border_mm / pixel_size_mm) - max_x_pix[1:4] += half_border_pix - max_x_pix[13:16] += half_border_pix - min_x_pix[10:13] -= half_border_pix - min_x_pix[4:7] -= half_border_pix + min_x_pix[1:4] -= half_border_pix + min_x_pix[13:16] -= half_border_pix + max_x_pix[10:13] += half_border_pix + max_x_pix[4:7] += half_border_pix - # Positive side of 4/5/6, 16/17/18, 7/8/9, same as negative side of 13/14/15, 7/8/9, - # 16/17/18 + # Negative side of 4/5/6, 16/17/18, same as positive side of 13/14/15, 7/8/9 + # Also add this same chip gap to the outside chips. Neg side of 7/8/9, pos 16/17/18. border_mm = abs(sca_xc_mm[7]-sca_xc_mm[4])-galsim.wfirst.n_pix*pixel_size_mm half_border_pix = int(0.5*border_mm / pixel_size_mm) - max_x_pix[4:10] += half_border_pix - max_x_pix[16:19] += half_border_pix - min_x_pix[7:10] -= half_border_pix - min_x_pix[13:19] -= half_border_pix + min_x_pix[4:10] -= half_border_pix + min_x_pix[16:19] -= half_border_pix + max_x_pix[7:10] += half_border_pix + max_x_pix[13:19] += half_border_pix # In the vertical direction, the gaps vary, with the gap between one pair of rows being # significantly larger than between the other pair of rows. The reason for this has to do @@ -399,22 +400,24 @@ def _calculate_minmax_pix(include_border=False): # and choices in which way to arrange each SCA to maximize the usable space in the focal # plane. - # Top of 2/5/8/11/14/17, same as bottom of 1/4/7/10/13/16 and 2/5/8/11/14/17 + # Top of 2/5/8/11/14/17, same as bottom of 1/4/7/10/13/16. + # Also use this for top of top row: 1/4/7/10/13/16. border_mm = abs(sca_yc_mm[1]-sca_yc_mm[2])-galsim.wfirst.n_pix*pixel_size_mm half_border_pix = int(0.5*border_mm / pixel_size_mm) - list_1 = np.linspace(1,16,6).astype(int) + list_1 = np.arange(1,18,3) list_2 = list_1 + 1 list_3 = list_1 + 2 - min_y_pix[list_1] -= half_border_pix - min_y_pix[list_2] -= half_border_pix max_y_pix[list_2] += half_border_pix + min_y_pix[list_1] -= half_border_pix + max_y_pix[list_1] += half_border_pix - # Top of 1/4/7/10/13/16, same as bottom of 3/6/9/12/15/18 and top of same - border_mm = abs(sca_yc_mm[1]-sca_yc_mm[3])-galsim.wfirst.n_pix*pixel_size_mm + # Top of 3/6/9/12/15/18, same as bottom of 2/5/8/11/14/17. + # Also use this for bottom of bottom row: 3/6/9/12/15/18. + border_mm = abs(sca_yc_mm[2]-sca_yc_mm[3])-galsim.wfirst.n_pix*pixel_size_mm half_border_pix = int(0.5*border_mm / pixel_size_mm) - min_y_pix[list_3] -= half_border_pix - max_y_pix[list_1] += half_border_pix max_y_pix[list_3] += half_border_pix + min_y_pix[list_2] -= half_border_pix + min_y_pix[list_3] -= half_border_pix return min_x_pix, max_x_pix, min_y_pix, max_y_pix @@ -423,23 +426,25 @@ def _populate_required_fields(header): Utility routine to do populate some of the basic fields for the WCS headers for WFIRST that don't require any interesting calculation. """ - header['EQUINOX'] = (2000.0, "equinox of celestial coordinate system") - header['WCSAXES'] = (2, "number of World Coordinate System axes") - header['A_ORDER'] = 4 - header['B_ORDER'] = 4 - header['WCSNAME'] = 'wfiwcs_'+optics_design_ver+'_'+prog_version - header['CRPIX1'] = (galsim.wfirst.n_pix/2, "x-coordinate of reference pixel") - header['CRPIX2'] = (galsim.wfirst.n_pix/2, "y-coordinate of reference pixel") - header['CTYPE1'] = ("RA---TAN-SIP", "coordinate type for the first axis") - header['CTYPE2'] = ("DEC--TAN-SIP", "coordinate type for the second axis") - header['SIMPLE'] = 'True' - header['BITPIX'] = 16 - header['NAXIS'] = 0 - header['EXTEND'] = 'True' - header['BZERO'] = 0 - header['BSCALE'] = 1 - header['TELESCOP'] = (tel_name, "telescope used to acquire data") - header['INSTRUME'] = (instr_name, "identifier for instrument used to acquire data") + header.extend([ + ('EQUINOX', 2000.0, "equinox of celestial coordinate system"), + ('WCSAXES', 2, "number of World Coordinate System axes"), + ('A_ORDER', 4), + ('B_ORDER', 4), + ('WCSNAME', 'wfiwcs_'+optics_design_ver+'_'+prog_version), + ('CRPIX1', galsim.wfirst.n_pix/2, "x-coordinate of reference pixel"), + ('CRPIX2', galsim.wfirst.n_pix/2, "y-coordinate of reference pixel"), + ('CTYPE1', "RA---TAN-SIP", "coordinate type for the first axis"), + ('CTYPE2', "DEC--TAN-SIP", "coordinate type for the second axis"), + ('SIMPLE', 'True'), + ('BITPIX', 16), + ('NAXIS', 0), + ('EXTEND', 'True'), + ('BZERO', 0), + ('BSCALE', 1), + ('TELESCOP', tel_name, "telescope used to acquire data"), + ('INSTRUME', instr_name, "identifier for instrument used to acquire data"), + ]) def _parse_sip_file(file): """ @@ -487,8 +492,6 @@ def _det_to_tangplane_positions(x_in, y_in): """ img_dist_coeff = np.array([-1.0873e-2, 3.5597e-03, 3.6515e-02, -1.8691e-4]) - if not isinstance(x_in, galsim.Angle) or not isinstance(y_in, galsim.Angle): - raise TypeError("Input x_in and y_in are not galsim.Angles.") # The optical distortion model is defined in terms of separations in *degrees*. r_sq = (x_in/galsim.degrees)**2 + (y_in/galsim.degrees)**2 r = np.sqrt(r_sq) diff --git a/tests/.coveragerc b/tests/.coveragerc index 2f65ff1afbc..af30d247ca5 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -34,6 +34,7 @@ exclude_lines = # Don't complain if tests don't hit defensive checks of user input raise NotImplementedError raise TypeError + raise OSError # Don't complain about not hitting warning code if suppress_warnings is False: diff --git a/tests/test_detectors.py b/tests/test_detectors.py index 1d49fc5409c..b4b5252935e 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -38,8 +38,8 @@ def test_nonlinearity_basic(): im_save = im.copy() # Basic - exceptions / bad usage (invalid function, does not return NumPy array). - with assert_raises(ValueError): - im.applyNonlinearity(lambda x : 1.0) + assert_raises(ValueError, im.applyNonlinearity, lambda x : 1.0) + assert_raises(ValueError, im.applyNonlinearity, lambda x : np.array([1,2,3])) # Check for constant function as NLfunc im_new = im.copy() @@ -177,8 +177,9 @@ def test_recipfail_basic(): im_save = im.copy() # Basic - exceptions / bad usage. - with assert_raises(ValueError): - im.addReciprocityFailure(-1.0, 200, 1.0) + assert_raises(ValueError, im.addReciprocityFailure, -1.0, 200, 1.0) + assert_raises(ValueError, im.addReciprocityFailure, 1.0, -200, 1.0) + assert_raises(ValueError, im.addReciprocityFailure, 1.0, 200, -1.0) # Preservation of data type / scale / bounds im_new = im.copy() @@ -322,6 +323,11 @@ def test_IPC_basic(): im_new.array, im.array, err_msg="Image is altered for no IPC with edge_treatment = 'crop'" ) + assert_raises(ValueError, im_new.applyIPC, galsim.Image(2,2,init_value=1)) + assert_raises(ValueError, im_new.applyIPC, galsim.Image(3,3,init_value=-1)) + assert_raises(ValueError, im_new.applyIPC, ipc_kernel * -1) + assert_raises(ValueError, im_new.applyIPC, ipc_kernel, edge_treatment='invalid') + # Test with a scalar fill_value fill_value = np.pi # a non-trivial one im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop',fill_value=fill_value) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index ef46b5c774b..5ab6dc67889 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -429,13 +429,15 @@ def test_check_all_contiguous(): @timer def test_deInterleaveImage(): + from galsim.utilities import deInterleaveImage, interleaveImages + np.random.seed(84) # for generating the same random instances # 1) Check compatability with interleaveImages img = galsim.Image(np.random.randn(64,64),scale=0.25) img.setOrigin(galsim.PositionI(5,7)) ## for non-trivial bounds - im_list, offsets = galsim.utilities.deInterleaveImage(img,8) - img1 = galsim.utilities.interleaveImages(im_list,8,offsets) + im_list, offsets = deInterleaveImage(img,8) + img1 = interleaveImages(im_list,8,offsets) np.testing.assert_array_equal(img1.array,img.array, err_msg = "interleaveImages cannot reproduce the input to deInterleaveImage for square " "images") @@ -445,8 +447,8 @@ def test_deInterleaveImage(): img = galsim.Image(abs(np.random.randn(16*5,16*2)),scale=0.5) img.setCenter(0,0) ## for non-trivial bounds - im_list, offsets = galsim.utilities.deInterleaveImage(img,(2,5)) - img1 = galsim.utilities.interleaveImages(im_list,(2,5),offsets) + im_list, offsets = deInterleaveImage(img,(2,5)) + img1 = interleaveImages(im_list,(2,5),offsets) np.testing.assert_array_equal(img1.array,img.array, err_msg = "interleaveImages cannot reproduce the input to deInterleaveImage for " "rectangular images") @@ -456,7 +458,7 @@ def test_deInterleaveImage(): # 2) Checking for offsets img = galsim.Image(np.random.randn(32,32),scale=2.0) - im_list, offsets = galsim.utilities.deInterleaveImage(img,(4,2)) + im_list, offsets = deInterleaveImage(img,(4,2)) ## Checking if offsets are centered around zero assert np.sum([offset.x for offset in offsets]) == 0. @@ -473,7 +475,7 @@ def test_deInterleaveImage(): img0 = galsim.Image(32,32) g0.drawImage(image=img0,method='no_pixel',scale=0.25) - im_list0, offsets0 = galsim.utilities.deInterleaveImage(img0,2,conserve_flux=True) + im_list0, offsets0 = deInterleaveImage(img0,2,conserve_flux=True) for n in range(len(im_list0)): im = galsim.Image(16,16) @@ -496,8 +498,8 @@ def test_deInterleaveImage(): g1.drawImage(image=img1,scale=0.5/n1,method='no_pixel') g2.drawImage(image=img2,scale=0.5/n2,method='no_pixel') - im_list1, offsets1 = galsim.utilities.deInterleaveImage(img1,(n1**2,1),conserve_flux=True) - im_list2, offsets2 = galsim.utilities.deInterleaveImage(img2,[1,n2**2],conserve_flux=False) + im_list1, offsets1 = deInterleaveImage(img1,(n1**2,1),conserve_flux=True) + im_list2, offsets2 = deInterleaveImage(img2,[1,n2**2],conserve_flux=False) for n in range(n1**2): im, offset = im_list1[n], offsets1[n] @@ -514,9 +516,19 @@ def test_deInterleaveImage(): "horizontal direction") # im is scaled to account for flux not being conserved + assert_raises(TypeError, deInterleaveImage, image=img0.array, N=2) + assert_raises(TypeError, deInterleaveImage, image=img0, N=2.0) + assert_raises(TypeError, deInterleaveImage, image=img0, N=(2.0, 2.0)) + assert_raises(TypeError, deInterleaveImage, image=img0, N=(2,2,3)) + assert_raises(ValueError, deInterleaveImage, image=img0, N=7) + assert_raises(ValueError, deInterleaveImage, image=img0, N=(2,7)) + assert_raises(ValueError, deInterleaveImage, image=img0, N=(7,2)) + @timer def test_interleaveImages(): + from galsim.utilities import interleaveImages, deInterleaveImage + # 1a) With galsim Gaussian g = galsim.Gaussian(sigma=3.7,flux=1000.) gal = galsim.Convolve([g,galsim.Pixel(1.0)]) @@ -534,7 +546,7 @@ def test_interleaveImages(): scale = im.scale # Input to N as an int - img = galsim.utilities.interleaveImages(im_list,n,offsets=offset_list) + img = interleaveImages(im_list,n,offsets=offset_list) im = galsim.Image(16*n*n,16*n*n) g = galsim.Gaussian(sigma=3.7,flux=1000.*n*n) gal = galsim.Convolve([g,galsim.Pixel(1.0)]) @@ -560,7 +572,7 @@ def test_interleaveImages(): im_list_randperm = [im_list[idx] for idx in rand_idx] offset_list_randperm = [offset_list[idx] for idx in rand_idx] # Input to N as a tuple - img_randperm = galsim.utilities.interleaveImages(im_list_randperm,(n,n),offsets=offset_list_randperm) + img_randperm = interleaveImages(im_list_randperm,(n,n),offsets=offset_list_randperm) np.testing.assert_array_equal(img_randperm.array,img.array, err_msg="Interleaved images do not match when 'offsets' is supplied") @@ -571,7 +583,7 @@ def test_interleaveImages(): im_list = [] n = 5 # Generate approximate offsets - DX = np.array([-0.67,-0.33,0.,0.33,0.67]) + DX = np.array([-0.47,-0.23,0.,0.23,0.47]) DY = DX for dy in DY: for dx in DX: @@ -583,7 +595,9 @@ def test_interleaveImages(): N = (n,n) with assert_raises(ValueError): - galsim.utilities.interleaveImages(im_list,N,offset_list) + interleaveImages(im_list,N,offset_list) + # Can turn off the checks and just use these as they are with catch_offset_errors=False + interleaveImages(im_list,N,offset_list, catch_offset_errors=False) offset_list = [] im_list = [] @@ -600,7 +614,8 @@ def test_interleaveImages(): N = (n,n) with assert_raises(ValueError): - galsim.utilities.interleaveImages(im_list, N, offset_list) + interleaveImages(im_list, N, offset_list) + interleaveImages(im_list, N, offset_list, catch_offset_errors=False) # 2a) Increase resolution along one direction - square to rectangular images n = 2 @@ -619,8 +634,8 @@ def test_interleaveImages(): gal1.drawImage(im,offset=offset,method='no_pixel',scale=2.0) im_list.append(im) - img = galsim.utilities.interleaveImages(im_list, N=[1,n**2], offsets=offset_list, - add_flux=False, suppress_warnings=True) + img = interleaveImages(im_list, N=[1,n**2], offsets=offset_list, + add_flux=False, suppress_warnings=True) im = galsim.Image(16,16*n*n) # The interleaved image has the total flux averaged out since `add_flux = False' gal = galsim.Gaussian(sigma=3.7*n,flux=100.) @@ -647,7 +662,7 @@ def test_interleaveImages(): gal2.drawImage(im,offset=offset,method='no_pixel',scale=3.0) im_list.append(im) - img = galsim.utilities.interleaveImages(im_list, N=np.array([n**2,1]), offsets=offset_list, + img = interleaveImages(im_list, N=np.array([n**2,1]), offsets=offset_list, suppress_warnings=True) im = galsim.Image(16*n*n,16*n*n) gal = galsim.Gaussian(sigma=3.7,flux=100.*n*n) @@ -676,8 +691,8 @@ def test_interleaveImages(): im.setOrigin(3,3) # for non-trivial bounds im_list.append(im) - img = galsim.utilities.interleaveImages(im_list,N=n,offsets=offset_list) - im_list_1, offset_list_1 = galsim.utilities.deInterleaveImage(img, N=n) + img = interleaveImages(im_list,N=n,offsets=offset_list) + im_list_1, offset_list_1 = deInterleaveImage(img, N=n) for k in range(n**2): assert offset_list_1[k] == offset_list[k] @@ -688,14 +703,37 @@ def test_interleaveImages(): assert im_list[k].bounds == im_list_1[k].bounds # Checking for non-default flux option - img = galsim.utilities.interleaveImages(im_list,N=n,offsets=offset_list,add_flux=False) - im_list_2, offset_list_2 = galsim.utilities.deInterleaveImage(img,N=n,conserve_flux=True) + img = interleaveImages(im_list,N=n,offsets=offset_list,add_flux=False) + im_list_2, offset_list_2 = deInterleaveImage(img,N=n,conserve_flux=True) for k in range(n**2): assert offset_list_2[k] == offset_list[k] np.testing.assert_array_equal(im_list_2[k].array, im_list[k].array) assert im_list_2[k].wcs == im_list[k].wcs + assert_raises(TypeError, interleaveImages, im_list=img, N=n, offsets=offset_list) + assert_raises(ValueError, interleaveImages, [img], N=1, offsets=offset_list) + assert_raises(TypeError, interleaveImages, [im.array for im in im_list], n, offset_list) + assert_raises(TypeError, interleaveImages, + [im_list[0]] + [im.array for im in im_list[1:]], + n, offset_list) + assert_raises(TypeError, interleaveImages, + [galsim.Image(16+i,16+j,scale=1) for i in range(n) for j in range(n)], + n, offset_list) + assert_raises(TypeError, interleaveImages, + [galsim.Image(16,16,scale=i) for i in range(n) for j in range(n)], + n, offset_list) + assert_raises(TypeError, interleaveImages, im_list, N=3.0, offsets=offset_list) + assert_raises(TypeError, interleaveImages, im_list, N=(3.0, 3.0), offsets=offset_list) + assert_raises(TypeError, interleaveImages, im_list, N=(3,3,3), offsets=offset_list) + assert_raises(ValueError, interleaveImages, im_list, N=7, offsets=offset_list) + assert_raises(ValueError, interleaveImages, im_list, N=(2,7), offsets=offset_list) + assert_raises(ValueError, interleaveImages, im_list, N=(7,2), offsets=offset_list) + assert_raises(TypeError, interleaveImages, im_list, N=n) + assert_raises(TypeError, interleaveImages, im_list, N=n, offsets=offset_list[0]) + assert_raises(TypeError, interleaveImages, im_list, N=n, offsets=range(n*n)) + + @timer def test_python_LRU_Cache(): diff --git a/tests/test_wfirst.py b/tests/test_wfirst.py index e65b93a1281..d96e3266b6b 100644 --- a/tests/test_wfirst.py +++ b/tests/test_wfirst.py @@ -157,9 +157,12 @@ def test_wfirst_wcs(): pa = test_data[4,:] chris_sca = test_data[5,:] n_test = len(ra_cen) + if __name__ != "__main__": + n_test = 3 # None of the first 3 fail, so the nfail test is ok. (Only 2 fail in all 100.) n_fail = 0 for i_test in range(n_test): + print('i_test = ',i_test) # Make the WCS for this test. world_pos = galsim.CelestialCoord(ra_cen[i_test]*galsim.degrees, dec_cen[i_test]*galsim.degrees) @@ -177,9 +180,43 @@ def test_wfirst_wcs(): galsim.CelestialCoord(ra[i_test]*galsim.degrees, dec[i_test]*galsim.degrees)) if found_sca is None: found_sca=0 - if found_sca != chris_sca[i_test]: n_fail += 1 + if found_sca != chris_sca[i_test]: + n_fail += 1 + print('Failed to find SCA: ',found_sca, chris_sca[i_test]) + + # Just cycle through the SCAs for the next bits. + sca_test = i_test % 18 + 1 + gs_wcs = gs_wcs_dict[sca_test] + + # Check center position: + im_cent_pos = galsim.PositionD(galsim.wfirst.n_pix/2., galsim.wfirst.n_pix/2) + gs_cent_pos = gs_wcs.toWorld(im_cent_pos) + + # Check pixel area + pix_area = gs_wcs.pixelArea(image_pos=im_cent_pos) + print('pix_area = ',pix_area) + np.testing.assert_allclose(pix_area, 0.012, atol=0.001) + + if i_test == 0: + # For just one of our tests cases, we'll do some additional tests. These will target + # the findSCA() functionality. First, check that the center is found in that SCA. + found_sca = galsim.wfirst.findSCA(gs_wcs_dict, gs_cent_pos) + np.testing.assert_equal(found_sca, sca_test, + err_msg='Did not find SCA center position to be on that SCA!') + + # Then, we go to a place that should be off the side by a tiny bit, and check that it is + # NOT on an SCA if we exclude borders, but IS on the SCA if we include borders. + im_off_edge_pos = galsim.PositionD(-2., galsim.wfirst.n_pix/2.) + world_off_edge_pos = gs_wcs.toWorld(im_off_edge_pos) + found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos) + assert found_sca is None + found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos, include_border=True) + np.testing.assert_equal(found_sca, sca_test, + err_msg='Did not find slightly off-edge position on the SCA' + ' when including borders!') # There were few-arcsec offsets in our WCS, so allow some fraction of failures. + print('n_fail = ',n_fail) assert n_fail < 0.05*n_test, 'Failed in SCA-matching against reference' # Check whether we're allowed to look at certain positions on certain dates. @@ -206,6 +243,30 @@ def test_wfirst_wcs(): pa = galsim.wfirst.bestPA(pos, test_date) np.testing.assert_almost_equal(pa.rad, -np.pi/2, decimal=3) + sun_pos= galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees) + sun_pa = galsim.wfirst.bestPA(sun_pos, test_date) + assert sun_pa is None + + with assert_raises(TypeError): + galsim.wfirst.getWCS(world_pos=galsim.PositionD(300,400)) + with assert_raises(galsim.GalSimError): + galsim.wfirst.getWCS(world_pos=sun_pos, date=test_date) + with assert_raises(TypeError): + galsim.wfirst.getWCS(world_pos=pos, PA=33.) + + # Check the rather bizarre convention that LONPOLE is always 180 EXCEPT (!!) when + # observing directly at the south pole. Apparently, this convention comes from the WFIRST + # project office's use of the LONPOLE keyword. So we keep it, even though it's stupid. + # cf. https://github.com/GalSim-developers/GalSim/pull/651#discussion-diff-26277673 + assert gs_wcs_dict[1].header['LONPOLE'] == 180. + south_pole = galsim.CelestialCoord(0*galsim.degrees, -90*galsim.degrees) + wcs = galsim.wfirst.getWCS(world_pos=south_pole, SCAs=1) + assert wcs[1].header['LONPOLE'] == 0 + + with assert_raises(TypeError): + galsim.wfirst.findSCA(wcs_dict=None, world_pos=pos) + with assert_raises(TypeError): + galsim.wfirst.findSCA(wcs_dict=wcs, world_pos=galsim.PositionD(300,400)) @timer def test_wfirst_backgrounds(): @@ -227,6 +288,19 @@ def test_wfirst_backgrounds(): bp, world_pos=galsim.CelestialCoord(180.*galsim.degrees, 5.*galsim.degrees), date=datetime.date(2025,9,15)) + # world_pos must be a CelestialCoord. + with assert_raises(TypeError): + galsim.wfirst.getSkyLevel(bp, world_pos=galsim.PositionD(300,400)) + + # No world_pos works. Produces sky level for some plausible generic location. + sky_level = galsim.wfirst.getSkyLevel(bp) + print('sky_level = ',sky_level) + np.testing.assert_allclose(sky_level, 6233.47369567) # regression test relative to v1.6 + + # But not with a non-wfirst bandpass + with assert_raises(galsim.GalSimError): + galsim.wfirst.getSkyLevel(galsim.Bandpass('wave', 'nm', 400, 550)) + # The routine should have some obvious symmetry, for example, ecliptic latitude above vs. below # plane and ecliptic longitude positive vs. negative (or vs. 360 degrees - original value). # Because of how equatorial and ecliptic coordinates are related on the adopted date, we can do @@ -263,8 +337,7 @@ def test_wfirst_bandpass(): for filter_name, filter_ in bp.items(): mag = AB_sed.calculateMagnitude(bandpass=filter_) np.testing.assert_almost_equal(mag,0.0,decimal=6, - err_msg="Zeropoint not set accurately enough for bandpass filter \ - {0}".format(filter_name)) + err_msg="Zeropoint not set accurately enough for bandpass filter "+filter_name) # Do a slightly less trivial check of bandpass-related calculations: # Jeff Kruk (at Goddard) took an SED template from the Castelli-Kurucz library, normalized it to @@ -329,10 +402,38 @@ def test_wfirst_bandpass(): for key in ref_zp.keys(): galsim_zp = bp[key].zeropoint + delta_zp # They use slightly different versions of the bandpasses, so we only require agreement to - # 0.1 mag. - np.testing.assert_almost_equal(galsim_zp, ref_zp[key], decimal=1, - err_msg="Zeropoint not as expected for bandpass " - "{0}".format(key)) + # 0.05 mag. + print('zp for %s: '%key, galsim_zp, ref_zp[key]) + np.testing.assert_allclose(galsim_zp, ref_zp[key], atol=0.05, + err_msg="Wrong zeropoint for bandpass "+key) + + # Note: the difference is not due to our default thinning. This isn't any better. + nothin_bp = galsim.wfirst.getBandpasses(AB_zeropoint=True, default_thin_trunc=False) + for key in ref_zp.keys(): + galsim_zp = nothin_bp[key].zeropoint + delta_zp + print('nothin zp for %s: '%key, galsim_zp, ref_zp[key]) + np.testing.assert_allclose(galsim_zp, ref_zp[key], atol=0.05, + err_msg="Wrong zeropoint for bandpass "+key) + + # Even with fairly extreme thinning, the error is still only 0.07 mag. + verythin_bp = galsim.wfirst.getBandpasses(AB_zeropoint=True, default_thin_trunc=False, + relative_throughput=0.05, rel_err=0.1) + for key in ref_zp.keys(): + galsim_zp = verythin_bp[key].zeropoint + delta_zp + print('verythin zp for %s: '%key, galsim_zp, ref_zp[key]) + np.testing.assert_allclose(galsim_zp, ref_zp[key], atol=0.07, + err_msg="Wrong zeropoint for bandpass "+key) + + with assert_raises(TypeError): + galsim.wfirst.getBandpasses(default_thin_trunc=False, rel_tp=0.05) + with assert_warns(galsim.GalSimWarning): + galsim.wfirst.getBandpasses(relative_throughput=0.05, rel_err=0.1) + + # Can also not bother to set the zeropoint. + nozp_bp = galsim.wfirst.getBandpasses(AB_zeropoint=False) + for key in nozp_bp: + assert nozp_bp[key].zeropoint is None + @timer def test_wfirst_detectors(): @@ -412,6 +513,17 @@ def test_wfirst_detectors(): im_2.array, im_1.array, err_msg='IPC results depend on function used.') + # Finally, just check that this runs. + # (Accuracy of component functionality is all tested elsewhere.) + npersist = len(galsim.wfirst.persistence_coefficients) + print('ncoeff for persistence: ', npersist) + ntest = npersist + 3 # Just need a few more to test that we keep npersist. + past_images = [] + for i in range(ntest): + im = obj.drawImage(scale=galsim.wfirst.pixel_scale) + past_images = galsim.wfirst.allDetectorEffects(im, past_images, rng=rng) + assert len(past_images) == npersist + @timer def test_wfirst_psfs(): @@ -451,7 +563,8 @@ def test_wfirst_psfs(): # like a test of the chromatic functionality, but there are ways that getPSF() could mess up # inputs such that there is a disagreement. That's why this unit test belongs here. use_sca = 5 - use_lam = 900. # nm + bp = galsim.wfirst.getBandpasses() + use_lam = bp['Y106'].effective_wavelength wfirst_psfs_chrom = galsim.wfirst.getPSF(SCAs=use_sca, approximate_struts=True) psf_chrom = wfirst_psfs_chrom[use_sca] @@ -470,13 +583,16 @@ def test_wfirst_psfs(): np.testing.assert_array_almost_equal( im_chrom.array, im_achrom.array, decimal=8, err_msg='PSF at a given wavelength and chromatic one evaluated at that wavelength disagree.') + wfirst_psfs_achrom2 = galsim.wfirst.getPSF(SCAs=use_sca, approximate_struts=True, + wavelength=bp['Y106']) # This is equivalent. + psf_achrom2 = wfirst_psfs_achrom[use_sca] + assert psf_achrom2 == psf_achrom # Make a very limited check that interpolation works: just 2 wavelengths, 1 SCA. # use the blue and red limits for Y106: - bp = galsim.wfirst.getBandpasses() blue_limit = bp['Y106'].blue_limit red_limit = bp['Y106'].red_limit - n_waves = 2 + n_waves = 3 other_sca = 12 wfirst_psfs_int = galsim.wfirst.getPSF(SCAs=[use_sca, other_sca], approximate_struts=True, n_waves=n_waves, @@ -484,7 +600,7 @@ def test_wfirst_psfs(): psf_int = wfirst_psfs_int[use_sca] # Check that evaluation at a single wavelength is consistent with previous results. im_int = im_achrom.copy() - obj_int = psf_int.evaluateAtWavelength(blue_limit) + obj_int = psf_int.evaluateAtWavelength(use_lam) im_int = obj_int.drawImage(image=im_int, scale=galsim.wfirst.pixel_scale) # These images should agree well, but not perfectly. One of them comes from drawing an image # from an object directly, whereas the other comes from drawing an image of that object, making @@ -498,48 +614,79 @@ def test_wfirst_psfs(): err_msg='PSF at a given wavelength and interpolated chromatic one evaluated at that ' 'wavelength disagree.') + # Check some invalid inputs. + # Note, this is a total cheat for getting test coverage of the high_accuracy and + # non-approximate_struts branches in getPSF. The actual test of this functionality comes + # below, but it is only run for __name__==__main__ runs (i.e. run_all_tests). + with assert_raises(galsim.GalSimIncompatibleValuesError): + galsim.wfirst.getPSF(SCAs=use_sca, n_waves=2, + approximate_struts=False, high_accuracy=True, + wavelength_limits=(red_limit, blue_limit)) + with assert_raises(TypeError): + galsim.wfirst.getPSF(SCAs=use_sca, n_waves=2, + approximate_struts=True, high_accuracy=True, + wavelength_limits=red_limit) + with assert_raises(TypeError): + galsim.wfirst.getPSF(SCAs=use_sca, + approximate_struts=False, high_accuracy=False, + wavelength='Y106') + # This is a little slow, but we do want to run this as part of normal unit testing # to cover the storePSFImage and loadPSFImages functions. - if True: - #if __name__ == '__main__': - # Check that if we store and reload, what we get back is consistent with what we put in. - test_file = 'tmp_store.fits' - # Make sure we clear out any old versions - import os - if os.path.exists(test_file): - os.remove(test_file) - full_bp_list = galsim.wfirst.getBandpasses() - bp_list = ['Y106'] - galsim.wfirst.storePSFImages(bandpass_list=bp_list, PSF_dict=wfirst_psfs_int, - filename=test_file) - new_dict = galsim.wfirst.loadPSFImages(test_file) - # Check that it contains the right list of bandpasses. - np.testing.assert_array_equal( - list(new_dict.keys()), bp_list, err_msg='Wrong list of bandpasses in stored file') - # Check that when we take the dict for that bandpass, we get the right list of SCAs. - np.testing.assert_array_equal( - list(new_dict[bp_list[0]].keys()), list(wfirst_psfs_int.keys()), - err_msg='Wrong list of SCAs in stored file') - # Now draw an image from the stored object. - img_stored = new_dict[bp_list[0]][other_sca].drawImage(scale=1.3*galsim.wfirst.pixel_scale) - # Make a comparable image from the original interpolated object. This requires convolving with - # a star that has a flat SED. - star = galsim.Gaussian(sigma=1.e-8, flux=1.) - star_sed = galsim.SED(lambda x:1, - wave_type='nanometers', - flux_type='flambda').withFlux(1, full_bp_list[bp_list[0]]) - obj = galsim.Convolve(wfirst_psfs_int[other_sca], star*star_sed) - test_im = img_stored.copy() - test_im = obj.drawImage(full_bp_list[bp_list[0]], - image=test_im, scale=1.3*galsim.wfirst.pixel_scale) - # We have made some approximations here, so we cannot expect it to be great. - # Request 1% accuracy. - np.testing.assert_array_almost_equal( - img_stored.array, test_im.array, decimal=2, - err_msg='PSF from stored file and actual PSF object disagree.') - - # Delete test files when done. - os.remove(test_file) + + # Check that if we store and reload, what we get back is consistent with what we put in. + test_file = 'tmp_store.fits' + with open(test_file, 'wb'): pass # Just make it exist to test clobber feature. + full_bp_list = galsim.wfirst.getBandpasses() + bp_list = ['Y106'] + galsim.wfirst.storePSFImages(bandpass_list=bp_list, PSF_dict=wfirst_psfs_int, + filename=test_file, clobber=True) + new_dict = galsim.wfirst.loadPSFImages(test_file) + # Check that it contains the right list of bandpasses. + np.testing.assert_array_equal( + list(new_dict.keys()), bp_list, err_msg='Wrong list of bandpasses in stored file') + # Check that when we take the dict for that bandpass, we get the right list of SCAs. + np.testing.assert_array_equal( + list(new_dict[bp_list[0]].keys()), list(wfirst_psfs_int.keys()), + err_msg='Wrong list of SCAs in stored file') + # Now draw an image from the stored object. + img_stored = new_dict[bp_list[0]][other_sca].drawImage(scale=1.3*galsim.wfirst.pixel_scale) + # Make a comparable image from the original interpolated object. This requires convolving with + # a star that has a flat SED. + star = galsim.Gaussian(sigma=1.e-8, flux=1.) + star_sed = galsim.SED(lambda x:1, + wave_type='nanometers', + flux_type='flambda').withFlux(1, full_bp_list[bp_list[0]]) + obj = galsim.Convolve(wfirst_psfs_int[other_sca], star*star_sed) + test_im = img_stored.copy() + test_im = obj.drawImage(full_bp_list[bp_list[0]], + image=test_im, scale=1.3*galsim.wfirst.pixel_scale) + # We have made some approximations here, so we cannot expect it to be great. + # Request 1% accuracy. + np.testing.assert_array_almost_equal( + img_stored.array, test_im.array, decimal=2, + err_msg='PSF from stored file and actual PSF object disagree.') + + # Delete test files when done. + os.remove(test_file) + + # Now can write to that without clobber. + galsim.wfirst.storePSFImages(wfirst_psfs_int, test_file, bp_list) + assert os.path.isfile(test_file) + # Then without clobber, raises error, since file exists. + with assert_raises(OSError): + galsim.wfirst.storePSFImages(wfirst_psfs_int, test_file, bp_list) + + with assert_raises(galsim.GalSimError): + galsim.wfirst.storePSFImages({}, test_file, bp_list, clobber=True) + with assert_raises(TypeError): + galsim.wfirst.storePSFImages(wfirst_psfs_int, test_file, [1,2,3], clobber=True) + with assert_raises(galsim.GalSimValueError): + galsim.wfirst.storePSFImages(wfirst_psfs_int, test_file, ['g','r','i','z'], clobber=True) + # Note: another coverage cheat. Leaving out bandpass_list does all bands. + # It takes a long time to write them all out for real though, so we don't do so here. + with assert_raises(galsim.GalSimValueError): + galsim.wfirst.storePSFImages(wfirst_psfs_achrom, test_file, clobber=True) # Test the construction of PSFs with high_accuracy and/or not approximate_struts # But only if we're running from the command line. @@ -600,69 +747,54 @@ def test_wfirst_basic_numbers(): ref_pupil_plane_file = os.path.join(galsim.meta_data.share_dir, "WFIRST-AFTA_Pupil_Mask_C5_20141010_PLT.fits.gz") ref_stray_light_fraction = 0.1 - ref_ipc_kernel = np.array([ [0.001269938, 0.015399776, 0.001199862], \ - [0.013800177, 1.0, 0.015600367], \ - [0.001270391, 0.016129619, 0.001200137] ]) + ref_ipc_kernel = np.array([ [0.001269938, 0.015399776, 0.001199862], + [0.013800177, 1.0, 0.015600367], + [0.001270391, 0.016129619, 0.001200137] ]) ref_ipc_kernel /= np.sum(ref_ipc_kernel) ref_ipc_kernel = galsim.Image(ref_ipc_kernel) - ref_persistence_coefficients = \ - np.array([0.045707683,0.014959818,0.009115737,0.00656769,0.005135571, - 0.004217028,0.003577534,0.003106601])/100. + ref_persistence_coefficients = np.array( + [0.045707683,0.014959818,0.009115737,0.00656769,0.005135571, + 0.004217028,0.003577534,0.003106601])/100. ref_n_sca = 18 ref_n_pix_tot = 4096 ref_n_pix = 4088 ref_jitter_rms = 0.014 ref_charge_diffusion = 0.1 - assert galsim.wfirst.gain==ref_gain, \ - 'WFIRST gain disagrees with expected value' - assert galsim.wfirst.pixel_scale==ref_pixel_scale, \ - 'WFIRST pixel scale disagrees with expected value' - assert galsim.wfirst.diameter==ref_diameter, \ - 'WFIRST diameter disagrees with expected value' - assert galsim.wfirst.obscuration==ref_obscuration, \ - 'WFIRST obscuration disagrees with expected value' - assert galsim.wfirst.exptime==ref_exptime, \ - 'WFIRST exptime disagrees with expected value' - assert galsim.wfirst.dark_current==ref_dark_current, \ - 'WFIRST dark current disagrees with expected value' - assert galsim.wfirst.nonlinearity_beta==ref_nonlinearity_beta, \ - 'WFIRST nonlinearity disagrees with expected value' - assert galsim.wfirst.reciprocity_alpha==ref_reciprocity_alpha, \ - 'WFIRST reciprocity alpha disagrees with expected value' - assert galsim.wfirst.read_noise==ref_read_noise, \ - 'WFIRST read noise disagrees with expected value' - assert galsim.wfirst.n_dithers==ref_n_dithers, \ - 'WFIRST n_dithers disagrees with expected value' - assert galsim.wfirst.thermal_backgrounds.keys()==ref_thermal_backgrounds.keys(),\ - 'WFIRST thermal background list of filters disagrees with reference' + assert galsim.wfirst.gain==ref_gain + assert galsim.wfirst.pixel_scale==ref_pixel_scale + assert galsim.wfirst.diameter==ref_diameter + assert galsim.wfirst.obscuration==ref_obscuration + assert galsim.wfirst.exptime==ref_exptime + assert galsim.wfirst.dark_current==ref_dark_current + assert galsim.wfirst.nonlinearity_beta==ref_nonlinearity_beta + assert galsim.wfirst.reciprocity_alpha==ref_reciprocity_alpha + assert galsim.wfirst.read_noise==ref_read_noise + assert galsim.wfirst.n_dithers==ref_n_dithers + assert galsim.wfirst.thermal_backgrounds.keys()==ref_thermal_backgrounds.keys() for key in ref_thermal_backgrounds.keys(): - assert galsim.wfirst.thermal_backgrounds[key]==ref_thermal_backgrounds[key],\ - 'WFIRST thermal background for %s disagrees with expected value'%key - assert galsim.wfirst.pupil_plane_file==ref_pupil_plane_file, \ - 'WFIRST pupil plane filename disagrees with reference' - assert galsim.wfirst.stray_light_fraction==ref_stray_light_fraction, \ - 'WFIRST stray_light_fraction disagrees with expected value' - np.testing.assert_array_equal(ref_ipc_kernel, galsim.wfirst.ipc_kernel, - 'WFIRST IPC kernel disagrees with expected value') - np.testing.assert_array_equal( - ref_persistence_coefficients, galsim.wfirst.persistence_coefficients, - 'WFIRST persistence coefficients disagree with expected value') - assert galsim.wfirst.n_sca==ref_n_sca, \ - 'WFIRST n_sca disagrees with expected value' - assert galsim.wfirst.n_pix_tot==ref_n_pix_tot, \ - 'WFIRST n_pix_tot disagrees with expected value' - assert galsim.wfirst.n_pix==ref_n_pix, \ - 'WFIRST n_pix disagrees with expected value' - assert galsim.wfirst.jitter_rms==ref_jitter_rms, \ - 'WFIRST jitter_rms disagrees with expected value' - assert galsim.wfirst.charge_diffusion==ref_charge_diffusion, \ - 'WFIRST charge_diffusion disagrees with expected value' + assert galsim.wfirst.thermal_backgrounds[key]==ref_thermal_backgrounds[key] + assert galsim.wfirst.pupil_plane_file==ref_pupil_plane_file + assert galsim.wfirst.stray_light_fraction==ref_stray_light_fraction + np.testing.assert_array_equal(ref_ipc_kernel, galsim.wfirst.ipc_kernel) + np.testing.assert_array_equal(ref_persistence_coefficients, + galsim.wfirst.persistence_coefficients) + assert galsim.wfirst.n_sca==ref_n_sca + assert galsim.wfirst.n_pix_tot==ref_n_pix_tot + assert galsim.wfirst.n_pix==ref_n_pix + assert galsim.wfirst.jitter_rms==ref_jitter_rms + assert galsim.wfirst.charge_diffusion==ref_charge_diffusion if __name__ == "__main__": + #import cProfile, pstats + #pr = cProfile.Profile() + #pr.enable() test_wfirst_wcs() test_wfirst_backgrounds() test_wfirst_bandpass() test_wfirst_detectors() test_wfirst_psfs() test_wfirst_basic_numbers() + #pr.disable() + #ps = pstats.Stats(pr).sort_stats('tottime') + #ps.print_stats(30) From 42c312c12ec6d0a9f55563402f260fc1ce06e37c Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 27 Apr 2018 01:36:36 -0400 Subject: [PATCH 44/96] Increase test coverage of bandpass (#755) --- tests/test_bandpass.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_bandpass.py b/tests/test_bandpass.py index 73d925f6254..b35cff14401 100644 --- a/tests/test_bandpass.py +++ b/tests/test_bandpass.py @@ -128,6 +128,10 @@ def test_Bandpass_basic(): blue_limit=700, red_limit=400) assert_raises(ValueError, galsim.Bandpass, throughput=lambda w: 1, wave_type='inches') assert_raises(ValueError, galsim.Bandpass, throughput=lambda w: 1, wave_type=units.Unit('Hz')) + assert_raises(ValueError, galsim.Bandpass, galsim.LookupTable([400,550], [0.4, 0.55], 'linear'), + wave_type='nm', blue_limit=300, red_limit=500) + assert_raises(ValueError, galsim.Bandpass, galsim.LookupTable([400,550], [0.4, 0.55], 'linear'), + wave_type='nm', blue_limit=500, red_limit=600) @timer @@ -300,6 +304,11 @@ def test_ne(): galsim.Bandpass(throughput=lt, wave_type='nm').withZeropoint(sed)] all_obj_diff(bps) + with assert_raises(galsim.GalSimValueError): + galsim.Bandpass(throughput=lt, wave_type='nm').withZeropoint('invalid') + with assert_raises(TypeError): + galsim.Bandpass(throughput=lt, wave_type='nm').withZeropoint(None) + @timer def test_thin(): @@ -359,6 +368,13 @@ def test_zp(): assert bp_tr.zeropoint is None, \ "Zeropoint erroneously preserved after truncating with explicit blue_limit" + with assert_raises(galsim.GalSimValueError): + bp_tr = bp.truncate(preserve_zp = 'False') + with assert_raises(galsim.GalSimValueError): + bp_tr = bp.truncate(preserve_zp = 43) + with assert_raises(galsim.GalSimIncompatibleValuesError): + galsim.Bandpass('1', 'nm', 400, 550).truncate(relative_throughput=1.e-4) + @timer def test_truncate_inputs(): From 4669445e5c84f64d5e1eb38fef9bf674c3277e61 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 27 Apr 2018 09:33:12 -0400 Subject: [PATCH 45/96] Don't guard against ImportError for yaml in tests that use it. (#755) --- requirements.txt | 2 +- test_requirements.txt | 1 + tests/test_catalog.py | 72 ++++++++++++++++++++++++++++---------- tests/test_config_value.py | 59 +++++-------------------------- tests/test_vonkarman.py | 7 +--- tests/test_zernike.py | 8 +---- tests/time_noise_pad.py | 7 +--- tests/time_zip.py | 7 +--- 8 files changed, 69 insertions(+), 94 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0126b13338b..f8c6562434f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ pybind11>=2.2 pip==9.0.3 # For now, pybind11 in conjunction with pip version 10.0 is broken. Use 9.0.3. # Not technically required, but useful. -pyyaml>=3.12 +pyyaml>=3.12 # This one is required to run tests. pandas>=0.20 # This is not in conda. Let pip install these. diff --git a/test_requirements.txt b/test_requirements.txt index cf91a082b96..d660888f576 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,3 +1,4 @@ +pyyaml>=3.12 pytest>=3.4 pytest-xdist>=1.19 pytest-timeout>=1.2 diff --git a/tests/test_catalog.py b/tests/test_catalog.py index f5e484ac3ab..f9b83f064cc 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -50,10 +50,54 @@ def test_basic_catalog(): do_pickle(cat) + cat2 = galsim.Catalog('catalog.fits', 'config_input', hdu=1, file_type='FITS') + assert cat2 == cat + + cat3 = galsim.Catalog('catalog.fits', 'config_input', _nobjects_only=True) + assert cat3 == cat + assert cat3.nobjects == cat3.getNObjects() == cat.nobjects + with assert_raises(AttributeError): + assert cat3.ncols + with assert_raises(AttributeError): + cat3.get(1,'angle2') + + with assert_raises(galsim.GalSimValueError): + galsim.Catalog('catalog.fita', 'config_input', file_type='invalid') + + with assert_raises(IndexError): + cat.get(-1, 'angle2') + with assert_raises(IndexError): + cat.get(3, 'angle2') + with assert_raises(KeyError): + cat.get(1, 'invalid') + with assert_raises(TypeError): + cat.get('val', 'angle2') + + cat2 = galsim.Catalog('catalog2.fits', 'config_input', hdu=2) + assert cat2.nobjects == cat.nobjects + np.testing.assert_array_equal(cat2.data, cat.data) + assert cat2 != cat + do_pickle(cat2) + + cat3 = galsim.Catalog('catalog2.fits', 'config_input', hdu='data') + assert cat3.nobjects == cat.nobjects + np.testing.assert_array_equal(cat3.data, cat.data) + assert cat3 != cat + assert cat3 != cat2 # Even though these are the same, it doesn't no 'data' is hdu 2. + do_pickle(cat3) + + cat2n = galsim.Catalog('catalog2.fits', 'config_input', hdu=2, _nobjects_only=True) + assert cat2n.nobjects == 3 + + with assert_raises((IOError, OSError)): + galsim.Catalog('invalid.fits', 'config_input') + @timer def test_basic_dict(): """Test basic operations on Dict.""" + import yaml + # Pickle d = galsim.Dict(dir='config_input', file_name='dict.p') np.testing.assert_equal(len(d), 4) @@ -79,24 +123,16 @@ def test_basic_dict(): do_pickle(d) # YAML - try: - import yaml - except ImportError as e: - # Raise a warning so this message shows up when doing pytest (or scons tests). - import warnings - warnings.warn("Unable to import yaml. Skipping yaml tests") - print("Caught ",e) - else: - d = galsim.Dict(dir='config_input', file_name='dict.yaml') - np.testing.assert_equal(len(d), 5) - np.testing.assert_equal(d.file_type, 'YAML') - np.testing.assert_equal(d['i'], 1) - np.testing.assert_equal(d.get('s'), 'Brian') - np.testing.assert_equal(d.get('s2', 'Grail'), 'Grail') # Not in dict. Use default. - np.testing.assert_almost_equal(d.get('f', 999.), 0.1) # In dict. Ignore default. - d2 = galsim.Dict(dir='config_input', file_name='dict.yaml', file_type='yaml') - assert d == d2 - do_pickle(d) + d = galsim.Dict(dir='config_input', file_name='dict.yaml') + np.testing.assert_equal(len(d), 5) + np.testing.assert_equal(d.file_type, 'YAML') + np.testing.assert_equal(d['i'], 1) + np.testing.assert_equal(d.get('s'), 'Brian') + np.testing.assert_equal(d.get('s2', 'Grail'), 'Grail') # Not in dict. Use default. + np.testing.assert_almost_equal(d.get('f', 999.), 0.1) # In dict. Ignore default. + d2 = galsim.Dict(dir='config_input', file_name='dict.yaml', file_type='yaml') + assert d == d2 + do_pickle(d) @timer diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 2e6b9d113e1..75434f9526b 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -120,14 +120,7 @@ def test_float_value(): 'image_center' : galsim.PositionD(0,0), } - test_yaml = True - try: - galsim.config.ProcessInput(config) - except ImportError: - # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict. - del config['input']['dict'][2] - galsim.config.ProcessInput(config) - test_yaml = False + galsim.config.ProcessInput(config) # Test direct values val1 = galsim.config.ParseValue(config,'val1',config, float)[0] @@ -315,12 +308,8 @@ def test_float_value(): dict = [] dict.append(galsim.config.ParseValue(config,'dict1',config, float)[0]) dict.append(galsim.config.ParseValue(config,'dict2',config, float)[0]) - if test_yaml: - dict.append(galsim.config.ParseValue(config,'dict3',config, float)[0]) - dict.append(galsim.config.ParseValue(config,'dict4',config, float)[0]) - else: - dict.append(0.1) - dict.append(1.9) + dict.append(galsim.config.ParseValue(config,'dict3',config, float)[0]) + dict.append(galsim.config.ParseValue(config,'dict4',config, float)[0]) np.testing.assert_array_almost_equal(dict, [ 23.17, -17.23, 0.1, 1.9 ]) sum1 = galsim.config.ParseValue(config,'sum1',config, float)[0] @@ -530,14 +519,7 @@ def test_int_value(): 'bad5' : { 'type' : 'Catalog' , 'num' : -1, 'col' : 'int1' }, } - test_yaml = True - try: - galsim.config.ProcessInput(config) - except ImportError: - # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict. - del config['input']['dict'][2] - galsim.config.ProcessInput(config) - test_yaml = False + galsim.config.ProcessInput(config) # Test direct values val1 = galsim.config.ParseValue(config,'val1',config, int)[0] @@ -684,10 +666,7 @@ def test_int_value(): dict = [] dict.append(galsim.config.ParseValue(config,'dict1',config, int)[0]) dict.append(galsim.config.ParseValue(config,'dict2',config, int)[0]) - if test_yaml: - dict.append(galsim.config.ParseValue(config,'dict3',config, int)[0]) - else: - dict.append(1) + dict.append(galsim.config.ParseValue(config,'dict3',config, int)[0]) np.testing.assert_array_equal(dict, [ 17, -23, 1 ]) sum1 = galsim.config.ParseValue(config,'sum1', config, int)[0] @@ -760,14 +739,7 @@ def test_bool_value(): 'bad3' : { 'type' : 'RandomBinomial', 'N' : 2 }, } - test_yaml = True - try: - galsim.config.ProcessInput(config) - except ImportError: - # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict. - del config['input']['dict'][2] - galsim.config.ProcessInput(config) - test_yaml = False + galsim.config.ProcessInput(config) # Test direct values val1 = galsim.config.ParseValue(config,'val1',config, bool)[0] @@ -866,10 +838,7 @@ def test_bool_value(): dict = [] dict.append(galsim.config.ParseValue(config,'dict1',config, bool)[0]) dict.append(galsim.config.ParseValue(config,'dict2',config, bool)[0]) - if test_yaml: - dict.append(galsim.config.ParseValue(config,'dict3',config, bool)[0]) - else: - dict.append(False) + dict.append(galsim.config.ParseValue(config,'dict3',config, bool)[0]) np.testing.assert_array_equal(dict, [ True, False, False ]) # Test bad values @@ -926,14 +895,7 @@ def test_str_value(): 'bad5' : { 'type' : 'List', 'items' : [ 'Beautiful', 'plumage!', 'Ay?' ], 'index' : 5 }, } - test_yaml = True - try: - galsim.config.ProcessInput(config) - except ImportError: - # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict. - del config['input']['dict'][2] - galsim.config.ProcessInput(config) - test_yaml = False + galsim.config.ProcessInput(config) # Test direct values val1 = galsim.config.ParseValue(config,'val1',config, str)[0] @@ -1003,10 +965,7 @@ def test_str_value(): dict = [] dict.append(galsim.config.ParseValue(config,'dict1',config, str)[0]) dict.append(galsim.config.ParseValue(config,'dict2',config, str)[0]) - if test_yaml: - dict.append(galsim.config.ParseValue(config,'dict3',config, str)[0]) - else: - dict.append('Brian') + dict.append(galsim.config.ParseValue(config,'dict3',config, str)[0]) np.testing.assert_array_equal(dict, [ 'Life', 'of', 'Brian' ]) with assert_raises(galsim.GalSimConfigError): diff --git a/tests/test_vonkarman.py b/tests/test_vonkarman.py index 12730148faf..a7e1e63cd94 100644 --- a/tests/test_vonkarman.py +++ b/tests/test_vonkarman.py @@ -21,14 +21,9 @@ import os import sys +import galsim from galsim_test_helpers import * -try: - import galsim -except ImportError: - sys.path.append(os.path.abspath(os.path.join(path, ".."))) - import galsim - @timer def test_vk(slow=False): diff --git a/tests/test_zernike.py b/tests/test_zernike.py index b81e013235a..0ff59780282 100644 --- a/tests/test_zernike.py +++ b/tests/test_zernike.py @@ -21,15 +21,9 @@ import os import sys +import galsim from galsim_test_helpers import * -try: - import galsim -except ImportError: - path, filename = os.path.split(__file__) - sys.path.append(os.path.abspath(os.path.join(path, ".."))) - import galsim - @timer def test_Zernike_orthonormality(): diff --git a/tests/time_noise_pad.py b/tests/time_noise_pad.py index 38a80faa059..aa27c1460d4 100644 --- a/tests/time_noise_pad.py +++ b/tests/time_noise_pad.py @@ -26,12 +26,7 @@ n_iter = 50 -try: - import galsim -except ImportError: - path, filename = os.path.split(__file__) - sys.path.append(os.path.abspath(os.path.join(path, ".."))) - import galsim +import galsim def funcname(): import inspect diff --git a/tests/time_zip.py b/tests/time_zip.py index c2d931eb705..a3ad29f7fd3 100644 --- a/tests/time_zip.py +++ b/tests/time_zip.py @@ -28,12 +28,7 @@ n_iter = 20 -try: - import galsim -except ImportError: - path, filename = os.path.split(__file__) - sys.path.append(os.path.abspath(os.path.join(path, ".."))) - import galsim +import galsim big_im = galsim.Image(5000, 5000) big_im_file = 'big_im_file.fits' From e09e4063513fed5afd0b6e322ac1edbfe94eaca6 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 27 Apr 2018 10:29:43 -0400 Subject: [PATCH 46/96] Increase test coverage of catalog (#755) --- galsim/__init__.py | 1 + galsim/catalog.py | 46 ++++----- galsim/errors.py | 16 +++ tests/config_input/catalog2.fits | Bin 0 -> 14400 bytes tests/config_input/catalog2.txt | 5 + tests/config_input/catalog3.txt | 3 + tests/config_input/dict.txt | 33 +++++++ tests/config_input/dict.yaml | 2 - tests/test_catalog.py | 163 ++++++++++++++++++++++++++----- 9 files changed, 219 insertions(+), 50 deletions(-) create mode 100644 tests/config_input/catalog2.fits create mode 100644 tests/config_input/catalog2.txt create mode 100644 tests/config_input/catalog3.txt create mode 100644 tests/config_input/dict.txt diff --git a/galsim/__init__.py b/galsim/__init__.py index ad4fc0aeb02..dab899767e0 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -103,6 +103,7 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError +from .errors import GalSimKeyError, GalSimIndexError from .errors import GalSimBoundsError, GalSimUndefinedBoundsError, GalSimImmutableError from .errors import GalSimIncompatibleValuesError, GalSimSEDError, GalSimHSMError from .errors import GalSimConfigError, GalSimConfigValueError diff --git a/galsim/catalog.py b/galsim/catalog.py index c20c10f90a7..8843c08ed2f 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -24,7 +24,7 @@ import os import numpy as np -from .errors import GalSimValueError +from .errors import GalSimValueError, GalSimKeyError, GalSimIndexError class Catalog(object): """A class storing the data from an input catalog. @@ -96,10 +96,14 @@ def __init__(self, file_name, dir=None, file_type=None, comments='#', hdu=1, # don't get proxied. Only callable methods are. So make method versions of these. def getNObjects(self) : return self.nobjects def isFits(self) : return self.isfits + def __len__(self) : return self.nobjects def readAscii(self, comments, _nobjects_only=False): """Read in an input catalog from an ASCII file. """ + if len(comments) > 1: + raise GalSimValueError('Invalid comments character', comments) + # If all we care about is nobjects, this is quicker: if _nobjects_only: # See the script devel/testlinecounting.py that tests several possibilities. @@ -109,8 +113,8 @@ def readAscii(self, comments, _nobjects_only=False): if (len(comments) == 1): c = comments[0] self.nobjects = sum(1 for line in f if line[0] != c) - else: - self.nobjects = sum(1 for line in f if not line.startswith(comments)) + else: # len(comments) == 0. No comments. + self.nobjects = sum(1 for line in f) return # Read in the data using the numpy convenience function @@ -136,16 +140,10 @@ def readFits(self, hdu, _nobjects_only=False): """ from ._pyfits import pyfits with pyfits.open(self.file_name) as fits: - raw_data = fits[hdu].data - self.names = raw_data.columns.names - self.nobjects = len(raw_data.field(self.names[0])) + self.data = fits[hdu].data.copy() + self.names = self.data.columns.names + self.nobjects = len(self.data) if (_nobjects_only): return - # The pyfits raw_data is a FITS_rec object, which isn't picklable, so we need to - # copy the fields into a new structure to make sure our Catalog is picklable. - # The simplest is probably a dict keyed by the field names, which we save as self.data. - self.data = {} - for name in self.names: - self.data[name] = raw_data.field(name) self.ncols = len(self.names) self.isfits = True @@ -160,18 +158,16 @@ def get(self, index, col): """ if self.isfits: if col not in self.names: - raise KeyError("Column %s is invalid for catalog %s"%(col,self.file_name)) + raise GalSimKeyError("Column %s is invalid for catalog %s"%(col,self.file_name)) if index < 0 or index >= self.nobjects: - raise IndexError("Object %d is invalid for catalog %s"%(index,self.file_name)) - if index >= len(self.data[col]): - raise IndexError("Object %d is invalid for column %s"%(index,col)) + raise GalSimIndexError("Object %d is invalid for catalog %s"%(index,self.file_name)) return self.data[col][index] else: icol = int(col) if icol < 0 or icol >= self.ncols: - raise IndexError("Column %d is invalid for catalog %s"%(icol,self.file_name)) + raise GalSimIndexError("Column %s is invalid for catalog %s"%(icol,self.file_name)) if index < 0 or index >= self.nobjects: - raise IndexError("Object %d is invalid for catalog %s"%(index,self.file_name)) + raise GalSimIndexError("Object %s is invalid for catalog %s"%(index,self.file_name)) return self.data[index, icol] def getFloat(self, index, col): @@ -186,10 +182,8 @@ def getInt(self, index, col): def __repr__(self): s = "galsim.Catalog(file_name=%r, file_type=%r"%(self.file_name, self.file_type) - if self.comments != '#': - s += ', comments=%r'%self.comments - if self.hdu != 1: - s += ', hdu=%r'%self.hdu + if self.comments != '#': s += ', comments=%r'%self.comments + if self.hdu != 1: s += ', hdu=%r'%self.hdu s += ')' return s @@ -305,10 +299,9 @@ def get(self, key, default=None): # Otherwise, return the result. else: if k not in d and default is None: - raise GalSimValueError("key not found in dictionary.",key) + raise GalSimKeyError("key not found in dictionary.",key) return d.get(k,default) - - raise GalSimValueError("Invalid key given to Dict.get()",key) + raise GalSimKeyError("Invalid key given to Dict.get()",key) # The rest of the functions are typical non-mutating functions for a dict, for which we just # pass the request along to self.dict. @@ -322,7 +315,7 @@ def __contains__(self, key): return key in self.dict def __iter__(self): - return self.dict.__iter__ + return self.dict.__iter__() def keys(self): return self.dict.keys() @@ -402,6 +395,7 @@ def __init__(self, names, types=None, _rows=(), _sort_keys=()): def nobjects(self): return len(self.rows) @property def ncols(self): return len(self.names) + def __len__(self): return self.nobjects # Again, when we use this through a proxy, we need getters for the attributes. def getNames(self): return self.names diff --git a/galsim/errors.py b/galsim/errors.py index 0c1a0ac20b5..64a19b2bca4 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -102,6 +102,22 @@ def __reduce__(self): # Need to override this whenever constructor take extra p return GalSimValueError, (self.message, self.value, self.allowed_values) +class GalSimKeyError(GalSimError, KeyError): + """A GalSim-specific exception class indicating an attempt to access a dict-like object + with an invalid key. + """ + def __repr__(self): + return 'galsim.GalSimKeyError(%r)'%(str(self)) + + +class GalSimIndexError(GalSimError, IndexError): + """A GalSim-specific execption class indicating an attempt to access a list-like object + with an invalid index. + """ + def __repr__(self): + return 'galsim.GalSimIndexError(%r)'%(str(self)) + + class GalSimRangeError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input value is outside of the allowed range of values. diff --git a/tests/config_input/catalog2.fits b/tests/config_input/catalog2.fits new file mode 100644 index 0000000000000000000000000000000000000000..e04d6253440efc154b0697dafa2f30fe7ef16a2e GIT binary patch literal 14400 zcmeI2L2uJA6vwk09FY(gE~b|p+iV5KVcJrbLQvOMYmf#K2X}G0XlYUCF7x~<$8VhK%ut@dNfVx3GpeXgr7`UxyquoYah4+_*m+%0ebOt zJY*?Jf|+tSW^ooKakdaC)SLs)>Fz4@yu!d0t5R&mu{aC~AOR$R1dsp{KmthM-zOl# z$${JJZV|KPc0Ie*am>P9)yXi7X?j999Y&0>mpl(n3qvke|7iTYe)afS#^aVEer|$Z z#NrV@67je{i^K_xR~4zZuF$g-dN5v9`VV_gyAa2(uwNLZ$#g;{G^Jz4Sz5kdc(hDU z_*?8hq+Yqo!mZbKozCu{+}}mLe8~hMi+r`J=RNB?az4ygEKS5071!V5i&w09?cV-g zuJ`2EC6)AGzty$(9JpWexwGFBl106G;gN`89xio>`>jh6;Q%Cn1dsp{Kmva~0RsyX zKmter2_OL^fCP{L5#rq{X^UoU!z5g`-Y+XDLxN816h!V;nbzIEv z&L?NpN+JIYZzai6O;6;<<@tgs{Xw;wp2&~qdiwlri1P|Fj0HU%{&GDn`FnZ3(46^` z3%@$QTu-0ByVBn+hP=Kci0>i%*)eg5uCe;H3Tt-oAvqhU*e z)$UjD+no8+wEl8Eef}QI{K12#V}5fzeg3S5_8vKzq3@Yy&75_ihgYscZ? sv6|BnE5A&(4ySM_SF!pO1VQWllFP9R2_OL^fCP{L5 Date: Fri, 27 Apr 2018 10:56:53 -0400 Subject: [PATCH 47/96] Increase test coverage of cdmodel (#755) --- tests/test_cdmodel.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_cdmodel.py b/tests/test_cdmodel.py index bc40bc43f71..3cd237a77b7 100644 --- a/tests/test_cdmodel.py +++ b/tests/test_cdmodel.py @@ -199,6 +199,29 @@ def test_simplegeometry(): "itcdrx array is not 0 where it should be") +@timer +def test_cdmodel_errors(): + """Test some invalid usage of CDModel""" + + # I don't think these errors are possible from the PowerLawCD constructor, so test + # them directly in the base class. + with assert_raises(galsim.GalSimValueError): + # Must be odd x odd + galsim.cdmodel.BaseCDModel( + np.zeros((4,4)), np.zeros((4,4)), np.zeros((4,4)), np.zeros((4,4)) ) + with assert_raises(galsim.GalSimValueError): + # Must be square + galsim.cdmodel.BaseCDModel( + np.zeros((5,3)), np.zeros((5,3)), np.zeros((5,3)), np.zeros((5,3)) ) + with assert_raises(galsim.GalSimValueError): + # Must be same shape + galsim.cdmodel.BaseCDModel( + np.zeros((3,3)), np.zeros((3,3)), np.zeros((3,3)), np.zeros((5,5)) ) + with assert_raises(galsim.GalSimValueError): + # Must be >= 3x3 + galsim.cdmodel.BaseCDModel( + np.zeros((1,1)), np.zeros((1,1)), np.zeros((1,1)), np.zeros((1,1)) ) + @timer def test_fluxconservation(): """Test flux conservation of charge deflection model for galaxy and flat image. @@ -347,6 +370,7 @@ def test_exampleimage(): if __name__ == "__main__": test_simplegeometry() + test_cdmodel_errors() test_fluxconservation() test_forwardbackward() test_gainratio() From f9e40ab86bfe97887c800425029d716172c8cad1 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 28 Apr 2018 12:41:11 -0400 Subject: [PATCH 48/96] Increase test coverage of chromatic (#755) --- galsim/chromatic.py | 109 +++++------- galsim/dcr.py | 25 ++- galsim/integ.py | 2 - galsim/random.py | 2 - galsim/transform.py | 44 ++--- galsim/utilities.py | 19 ++- tests/galsim_test_helpers.py | 46 +----- tests/test_chromatic.py | 312 +++++++++++++++++++++++++++++------ tests/test_integ.py | 4 + tests/test_photon_array.py | 29 +++- tests/test_real.py | 3 + tests/test_sed.py | 118 ++++++++++++- tests/test_utilities.py | 11 ++ 13 files changed, 507 insertions(+), 217 deletions(-) diff --git a/galsim/chromatic.py b/galsim/chromatic.py index a618987c7fe..849dfc504cc 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -33,6 +33,7 @@ from .bandpass import Bandpass from .position import PositionD, PositionI from .utilities import lazy_property +from .gsparams import GSParams from . import utilities from . import integ from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimValueError @@ -159,11 +160,7 @@ class ChromaticObject(object): # - .dimensionless indicates obj.SED.dimensionless def __init__(self, obj): - self.separable = obj.separable - self.interpolated = obj.interpolated - self.wave_list = obj.wave_list self._obj = obj - self.deinterpolated = obj.deinterpolated if isinstance(obj, GSObject): self.SED = SED(obj.flux, 'nm', '1') elif isinstance(obj, ChromaticObject): @@ -171,6 +168,10 @@ def __init__(self, obj): else: raise TypeError("Can only directly instantiate ChromaticObject with a GSObject " "or ChromaticObject argument.") + self.separable = obj.separable + self.interpolated = obj.interpolated + self.wave_list = obj.wave_list + self.deinterpolated = obj.deinterpolated @staticmethod def _get_multiplier(sed, bandpass, wave_list): @@ -210,9 +211,10 @@ def _fiducial_profile(self, bandpass): # Prioritize wavelengths near the bandpass effective wavelength. candidate_waves = candidate_waves[np.argsort(np.abs(candidate_waves - bpwave))] for w in candidate_waves: - prof0 = self.evaluateAtWavelength(w) - if prof0.flux != 0: - return w, prof0 + if bandpass.blue_limit <= w <= bandpass.red_limit: + prof0 = self.evaluateAtWavelength(w) + if prof0.flux != 0: + return w, prof0 raise GalSimError("Could not locate fiducial wavelength where SED * Bandpass is nonzero.") @@ -954,8 +956,11 @@ def shift(self, *args, **kwargs): if len(args) == 0: # Then dx,dy need to be kwargs # If not, then python will raise an appropriate error. - dx = kwargs.pop('dx') - dy = kwargs.pop('dy') + try: + dx = kwargs.pop('dx') + dy = kwargs.pop('dy') + except KeyError: + raise TypeError('shift() requires exactly 2 arguments (dx, dy)') offset = None elif len(args) == 1: if hasattr(args[0], '__call__'): @@ -975,7 +980,8 @@ def offset_func(w): offset = np.asarray( (args[0].x, args[0].y) ) else: # Let python raise the appropriate exception if this isn't valid. - offset = np.asarray(args[0]) + dx, dy = args[0] + offset = np.asarray( (dx, dy) ) elif len(args) == 2: dx = args[0] dy = args[1] @@ -1147,10 +1153,8 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', if _flux_ratio is None: _flux_ratio = lambda w: 1.0 - if not hasattr(_flux_ratio, '__call__'): - # Can't do _flux_ratio = lambda w: _flux_ratio, need a temporary variable. - tmp = _flux_ratio - _flux_ratio = lambda w: tmp + # Constant flux_ratio is alread an SED at this point, so can treat as function. + assert hasattr(_flux_ratio, '__call__') # setup output image (semi-arbitrarily using the bandpass effective wavelength). # Note: we cannot just use self._imageAtWavelength, because that routine returns an image @@ -1167,10 +1171,10 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', wave_objs += [_flux_ratio] wave_list, _, _ = utilities.combine_wave_list(wave_objs) - if np.min(wave_list) < np.min(self.waves): - raise GalSimRangeError("Requested wavelength is outside the allowed range.", - wave_list, np.min(self.waves), np.max(self.waves)) - if np.max(wave_list) > np.max(self.waves): + if ( np.min(wave_list) < np.min(self.waves) + or np.max(wave_list) > np.max(self.waves) ): # pragma: no cover + # MJ: I'm pretty sure it's impossible to hit this. + # But just in case I'm wrong, I'm leaving it here but with pragma: no cover. raise GalSimRangeError("Requested wavelength is outside the allowed range.", wave_list, np.min(self.waves), np.max(self.waves)) @@ -1495,7 +1499,7 @@ def new_offset(jac2, off1, off2): if gsparams is None: self.gsparams = self.original.gsparams if hasattr(self.original, 'gsparams') else None else: - self.gsparams = gsparams + self.gsparams = GSParams.check(gsparams) if self.interpolated: self.deinterpolated = ChromaticTransformation( @@ -1510,12 +1514,11 @@ def new_offset(jac2, off1, off2): def __eq__(self, other): if not (isinstance(other, ChromaticTransformation) and self.original == other.original and - self.gsparams == other.gsparams and - self._flux_ratio == other._flux_ratio): + self.gsparams == other.gsparams): return False # There's really no good way to check that two callables are equal, except if they literally - # point to the same object. So we'll just check for that for _jac and _offset. - for attr in ['_jac', '_offset']: + # point to the same object. So we'll just check for that for _jac, _offset, _flux_ratio. + for attr in ['_jac', '_offset', '_flux_ratio']: selfattr = getattr(self, attr) otherattr = getattr(other, attr) # For this attr, either both need to be chromatic or neither. @@ -1558,37 +1561,12 @@ def __repr__(self): self.original, jac, offset, self._flux_ratio, self.gsparams) def __str__(self): - from .wcs import JacobianWCS + from .transform import Transformation s = str(self.original) if hasattr(self._jac, '__call__'): s += '.transform(%s)'%self._jac else: - dudx, dudy, dvdx, dvdy = self._jac.ravel() - if dudx != 1 or dudy != 0 or dvdx != 0 or dvdy != 1: - # Figure out the shear/rotate/dilate calls that are equivalent. - jac = JacobianWCS(dudx,dudy,dvdx,dvdy) - scale, shear, theta, flip = jac.getDecomposition() - single = None - if flip: - single = 0 # Special value indicating to just use transform. - if abs(theta.rad) > 1.e-12: - if single is None: - single = '.rotate(%s)'%theta - else: - single = 0 - if shear.g > 1.e-12: - if single is None: - single = '.shear(%s)'%shear - else: - single = 0 - if abs(scale-1.0) > 1.e-12: - if single is None: - single = '.expand(%s)'%scale - else: - single = 0 - if single == 0: - single = '.transform(%s,%s,%s,%s)'%(dudx,dudy,dvdx,dvdy) - s += single + s += Transformation._str_from_jac(self._jac) if hasattr(self._offset, '__call__'): s += '.shift(%s)'%self._offset elif np.array_equal(self._offset,(0,0)): @@ -1966,7 +1944,7 @@ def __init__(self, *args, **kwargs): for obj in self.obj_list: if not obj.separable and not isinstance(obj, ChromaticSum): n_nonsep += 1 if obj.interpolated: n_interp += 1 - if n_nonsep>1 and n_interp>0: # pragma: no cover + if n_nonsep>1 and n_interp>0: import warnings warnings.warn( "Image rendering for this convolution cannot take advantage of " @@ -2154,25 +2132,24 @@ def drawImage(self, bandpass, image=None, integrator='trapezoidal', iimult=None, # Separate convolutants into a Convolution of inseparable profiles multiplied by the # wavelength-dependent normalization of separable profiles, and the achromatic part of # separable profiles. - insep_obj = Convolve([obj for obj in self.obj_list if not obj.separable], - gsparams=self.gsparams) - # Note that insep_obj should always exist, since purely separable ChromaticConvolutions were + insep_obj = [obj for obj in self.obj_list if not obj.separable] + + # Note that len(insep_obj) > 0, since purely separable ChromaticConvolutions were # already handled above. # Don't wrap in Convolution if not needed. Single item can draw itself better than # Convolution can. - if len(insep_obj.obj_list) == 1: - insep_obj = insep_obj.obj_list[0] + if len(insep_obj) == 1: + insep_obj = insep_obj[0] + else: + insep_obj = Convolve(insep_obj, gsparams=self.gsparams) sep_profs = [] for obj in self.obj_list: if not obj.separable: continue - if isinstance(obj, GSObject): - sep_profs.append(obj) - else: - wave0, prof0 = obj._fiducial_profile(bandpass) - sep_profs.append(prof0 / obj.SED(wave0)) - insep_obj *= obj.SED + wave0, prof0 = obj._fiducial_profile(bandpass) + sep_profs.append(prof0 / obj.SED(wave0)) + insep_obj *= obj.SED # Collapse inseparable profiles and chromatic normalizations into one effective profile # Note that at this point, insep_obj.SED should *not* be None. @@ -2495,8 +2472,8 @@ def __init__(self, lam, diam=None, lam_over_diam=None, aberrations=None, self.scale_unit = scale_unit # We have to require either diam OR lam_over_diam: - if (diam is None and lam_over_diam is None) or \ - (diam is not None and lam_over_diam is not None): + if ( (diam is None and lam_over_diam is None) or + (diam is not None and lam_over_diam is not None) ): raise GalSimIncompatibleValuesError( "Need to specify telescope diameter OR wavelength/diam ratio", diam=diam, lam_over_diam=lam_over_diam) @@ -2607,8 +2584,8 @@ def __init__(self, lam, diam=None, lam_over_diam=None, scale_unit=None, **kwargs scale_unit = AngleUnit.from_name(scale_unit) self.scale_unit = scale_unit - if (diam is None and lam_over_diam is None) or \ - (diam is not None and lam_over_diam is not None): + if ( (diam is None and lam_over_diam is None) or + (diam is not None and lam_over_diam is not None) ): raise GalSimIncompatibleValuesError( "Need to specify telescope diameter OR wavelength/diam ratio", diam=diam, lam_over_diam=lam_over_diam) diff --git a/galsim/dcr.py b/galsim/dcr.py index 6c1d04033b0..ed37c781484 100644 --- a/galsim/dcr.py +++ b/galsim/dcr.py @@ -97,8 +97,13 @@ def zenith_parallactic_angles(obj_coord, zenith_coord=None, HA=None, latitude=No if HA is None or latitude is None: raise GalSimIncompatibleValuesError( "Must provide either zenith_coord or (HA, latitude).", - HA=HA, latitude=latitude, zenith_coord=zenit_coord) + HA=HA, latitude=latitude, zenith_coord=zenith_coord) zenith_coord = CelestialCoord(HA + obj_coord.ra, latitude) + else: + if HA is not None or latitude is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both zenith_coord and (HA, latitude).", + HA=HA, latitude=latitude, zenith_coord=zenith_coord) zenith_angle = obj_coord.distanceTo(zenith_coord) NCP = CelestialCoord(0.0*degrees, 90*degrees) parallactic_angle = obj_coord.angleBetween(NCP, zenith_coord) @@ -133,19 +138,11 @@ def parse_dcr_angles(**kwargs): raise TypeError("parallactic_angle must be a galsim.Angles.") elif 'obj_coord' in kwargs: obj_coord = kwargs.pop('obj_coord') - if 'zenith_coord' in kwargs: - zenith_coord = kwargs.pop('zenith_coord') - zenith_angle, parallactic_angle = zenith_parallactic_angles( - obj_coord=obj_coord, zenith_coord=zenith_coord) - else: - if 'HA' not in kwargs or 'latitude' not in kwargs: - raise GalSimIncompatibleValuesError( - "Must provide either zenith_coord or (HA, latitude).", - HA=None, latitude=None, obj_coord=obj_coode) - HA = kwargs.pop('HA') - latitude = kwargs.pop('latitude') - zenith_angle, parallactic_angle = zenith_parallactic_angles( - obj_coord=obj_coord, HA=HA, latitude=latitude) + zenith_coord = kwargs.pop('zenith_coord', None) + HA = kwargs.pop('HA', None) + latitude = kwargs.pop('latitude', None) + zenith_angle, parallactic_angle = zenith_parallactic_angles( + obj_coord=obj_coord, zenith_coord=zenith_coord, HA=HA, latitude=latitude) else: raise TypeError("Need to specify zenith_angle and parallactic_angle.") return zenith_angle, parallactic_angle, kwargs diff --git a/galsim/integ.py b/galsim/integ.py index f3d1d1214be..1476069c2c5 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -192,8 +192,6 @@ def __init__(self, rule): self.rule = rule def calculateWaves(self, bandpass): - if len(bandpass.wave_list) < 0: - raise GalSimValueError("Provided bandpass must have defined wave_list", bandpass) return bandpass.wave_list diff --git a/galsim/random.py b/galsim/random.py index 01c6c3ef144..864969de059 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -757,8 +757,6 @@ def __init__(self, seed=None, function=None, x_min=None, # Quietly renormalize the probability if it wasn't already normalized totalprobability = cdf[-1] - if totalprobability < 0.: - raise GalSimValueError('Negative probability found in DistDeviate.',function) cdf /= totalprobability self._inverse_cdf = LookupTable(cdf, xarray, interpolant='linear') diff --git a/galsim/transform.py b/galsim/transform.py index 6a228bb7cb0..c99a3a56b26 100644 --- a/galsim/transform.py +++ b/galsim/transform.py @@ -198,39 +198,45 @@ def __repr__(self): return 'galsim.Transformation(%r, jac=%r, offset=%r, flux_ratio=%r, gsparams=%r)'%( self.original, self._jac.tolist(), self.offset, self.flux_ratio, self.gsparams) - def __str__(self): + @classmethod + def _str_from_jac(cls, jac): from .wcs import JacobianWCS - s = str(self.original) - dudx, dudy, dvdx, dvdy = self._jac.ravel() + dudx, dudy, dvdx, dvdy = jac.ravel() if dudx != 1 or dudy != 0 or dvdx != 0 or dvdy != 1: # Figure out the shear/rotate/dilate calls that are equivalent. jac = JacobianWCS(dudx,dudy,dvdx,dvdy) scale, shear, theta, flip = jac.getDecomposition() - single = None + s = None if flip: - single = 0 # Special value indicating to just use transform. + s = 0 # Special value indicating to just use transform. if abs(theta.rad) > 1.e-12: - if single is None: - single = '.rotate(%s)'%theta + if s is None: + s = '.rotate(%s)'%theta else: - single = 0 + s = 0 if shear.g > 1.e-12: - if single is None: - single = '.shear(%s)'%shear + if s is None: + s = '.shear(%s)'%shear else: - single = 0 + s = 0 if abs(scale-1.0) > 1.e-12: - if single is None: - single = '.expand(%s)'%scale + if s is None: + s = '.expand(%s)'%scale else: - single = 0 - if single == 0: + s = 0 + if s == 0: # If flip or there are two components, then revert to transform as simpler. - single = '.transform(%s,%s,%s,%s)'%(dudx,dudy,dvdx,dvdy) - if single is None: + s = '.transform(%s,%s,%s,%s)'%(dudx,dudy,dvdx,dvdy) + if s is None: # If nothing is large enough to show up above, give full detail of transform - single = '.transform(%r,%r,%r,%r)'%(dudx,dudy,dvdx,dvdy) - s += single + s = '.transform(%r,%r,%r,%r)'%(dudx,dudy,dvdx,dvdy) + return s + else: + return '' + + def __str__(self): + s = str(self.original) + s += self._str_from_jac(self._jac) if self.offset.x != 0 or self.offset.y != 0: s += '.shift(%s,%s)'%(self.offset.x,self.offset.y) if self.flux_ratio != 1.: diff --git a/galsim/utilities.py b/galsim/utilities.py index 7b0818ddd5a..b47b9334d66 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -26,7 +26,8 @@ import os import numpy as np -from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimRangeError +from .errors import GalSimWarning def roll2d(image, shape): @@ -404,16 +405,16 @@ def thin_tabulated_values(x, f, rel_err=1.e-4, trim_zeros=True, preserve_range=T # Nothing to do return x,f - if trim_zeros: - first = max(f.nonzero()[0][0]-1, 0) # -1 to keep one non-redundant zero. - last = min(f.nonzero()[0][-1]+1, len(x)-1) # +1 to keep one non-redundant zero. - x, f = x[first:last+1], f[first:last+1] - total_integ = np.trapz(abs(f), x) if total_integ == 0: return np.array([ x[0], x[-1] ]), np.array([ f[0], f[-1] ]) thresh = total_integ * rel_err + if trim_zeros: + first = max(f.nonzero()[0][0]-1, 0) # -1 to keep one non-redundant zero. + last = min(f.nonzero()[0][-1]+1, len(x)-1) # +1 to keep one non-redundant zero. + x, f = x[first:last+1], f[first:last+1] + x_range = x[-1] - x[0] if not preserve_range: # Remove values from the front that integrate to less than thresh. @@ -988,6 +989,8 @@ def resize(self, maxsize): else: root = self.root cache = self.cache + if maxsize <= 0: + raise GalSimValueError("Invalid maxsize", maxsize) if maxsize < oldsize: for i in range(oldsize - maxsize): # Delete root.next @@ -995,15 +998,13 @@ def resize(self, maxsize): new_next_link = root[1] = root[1][1] new_next_link[0] = root del cache[current_next_link[2]] - elif maxsize > oldsize: + else: # maxsize > oldsize: for i in range(maxsize - oldsize): # Insert between root and root.next key = object() cache[key] = link = [root, root[1], key, None] root[1][0] = link root[1] = link - else: - raise GalSimValueError("Invalid maxsize.", maxsize) # http://stackoverflow.com/questions/2891790/pretty-printing-of-numpy-array diff --git a/tests/galsim_test_helpers.py b/tests/galsim_test_helpers.py index daaf0b0d5cc..c8de6ccba1e 100644 --- a/tests/galsim_test_helpers.py +++ b/tests/galsim_test_helpers.py @@ -22,6 +22,7 @@ import sys import logging import coord +import copy path, filename = os.path.split(__file__) try: @@ -568,51 +569,6 @@ def all_obj_diff(objs, check_hash=True): raise e -def check_chromatic_invariant(obj, bps=None, waves=None): - """ Helper function to check that ChromaticObjects satisfy intended invariants. - """ - if bps is None: - # load a filter - bppath = os.path.join(galsim.meta_data.share_dir, 'bandpasses') - bandpass = (galsim.Bandpass(os.path.join(bppath, 'LSST_r.dat'), 'nm') - .truncate(relative_throughput=1e-3) - .thin(rel_err=1e-3)) - bps = [bandpass] - - if waves is None: - waves = [500.] - - assert isinstance(obj.wave_list, np.ndarray) - assert isinstance(obj.separable, bool) - assert isinstance(obj.interpolated, bool) - assert isinstance(obj.deinterpolated, (galsim.ChromaticObject, galsim.GSObject)) - - for wave in waves: - desired = obj.SED(wave) - # Since InterpolatedChromaticObject.evaluateAtWavelength involves actually drawing an - # image, which implies flux can be lost off of the edges of the image, we don't expect - # its accuracy to be nearly as good as for other objects. - decimal = 2 if obj.interpolated else 7 - np.testing.assert_almost_equal(obj.evaluateAtWavelength(wave).flux, desired, - decimal) - # Don't bother trying to draw a deconvolution. - if isinstance(obj, galsim.ChromaticDeconvolution): - continue - np.testing.assert_allclose( - obj.evaluateAtWavelength(wave).drawImage().array.sum(dtype=float), - desired, - rtol=1e-2) - - if obj.SED.spectral: - for bp in bps: - calc_flux = obj.calculateFlux(bp) - np.testing.assert_equal(obj.SED.calculateFlux(bp), calc_flux) - np.testing.assert_allclose(calc_flux, obj.drawImage(bp).array.sum(dtype=float), rtol=1e-2) - # Also try manipulating exptime and area. - np.testing.assert_allclose( - calc_flux * 10, obj.drawImage(bp, exptime=5, area=2).array.sum(dtype=float), rtol=1e-2) - - def funcname(): import inspect return inspect.stack()[1][3] diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 5f3305019c6..9e39e57fd09 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -168,21 +168,13 @@ def test_draw_add_commutativity(): # similar times in the test suite where we want to force it to use the base class # implementation, so those had to be switched as well. galsim.ChromaticObject.drawImage(chromatic_final, bandpass, image=chromatic_image, - integrator=integrator) + integrator=integrator, add_to_image=True) galsim.ChromaticObject.drawKImage(chromatic_final, bandpass, image=chromatic_kimage, integrator=integrator) t5 = time.time() print('ChromaticObject drawImage, drawKImage took {0} seconds.'.format(t5-t4)) # plotme(chromatic_image) - # Check error handling of too few sample points - integrator = galsim.integ.ContinuousIntegrator(galsim.integ.midptRule, N=1, use_endpoints=False) - with assert_raises(ValueError): - chromatic_final.drawImage(bandpass, integrator=integrator) - integrator = galsim.integ.ContinuousIntegrator(galsim.integ.trapzRule, N=1, use_endpoints=False) - with assert_raises(ValueError): - chromatic_final.drawImage(bandpass, integrator=integrator) - peak = chromatic_image.array.max() printval(GS_image, chromatic_image) np.testing.assert_array_almost_equal( @@ -196,6 +188,23 @@ def test_draw_add_commutativity(): err_msg="Directly computed chromatic kimage disagrees with kimage created using " +"galsim.chromatic") + # Repeat with multiple inseparable profiles. + delta = galsim.ChromaticObject(galsim.DeltaFunction()).rotate(lambda wave: wave*galsim.degrees) + chromatic_final2 = galsim.Convolve(chromatic_gal, chromatic_PSF, delta) + chromatic_final2.drawImage(bandpass, image=chromatic_image, integrator=integrator) + chromatic_final2.drawKImage(bandpass, image=chromatic_kimage, integrator=integrator) + # Note: fft vs real space differences now, so only accurate to 1.e-3 + np.testing.assert_array_almost_equal(chromatic_image.array/peak, GS_image.array/peak, 3) + np.testing.assert_array_almost_equal(chromatic_kimage.array/kpeak, GS_kimage.array/kpeak, 6) + + # Check error handling of too few sample points + integrator = galsim.integ.ContinuousIntegrator(galsim.integ.midptRule, N=1, use_endpoints=False) + with assert_raises(ValueError): + chromatic_final.drawImage(bandpass, integrator=integrator) + integrator = galsim.integ.ContinuousIntegrator(galsim.integ.trapzRule, N=1, use_endpoints=False) + with assert_raises(ValueError): + chromatic_final.drawImage(bandpass, integrator=integrator) + # As an aside, check for appropriate tests of 'integrator' argument. assert_raises(ValueError, chromatic_final.drawImage, bandpass, method='no_pixel', integrator='midp') # minor misspelling @@ -535,6 +544,26 @@ def test_chromatic_flux(): # As an aside, check for appropriate tests of 'integrator' argument. assert_raises(ValueError, final_int.drawImage, bandpass, integrator='midp') # minor misspelling assert_raises(TypeError, final_int.drawImage, bandpass, integrator=galsim.integ.midpt) + do_pickle(PSF) + + # Check option to not use exact SED + PSF = PSF.deinterpolated + PSF = PSF * 1.0 + PSF = PSF.interpolate(waves=np.linspace(bandpass.blue_limit, bandpass.red_limit, 30), + use_exact_SED=False) + final_int = galsim.Convolve([star, PSF]) + image3 = galsim.ImageD(stamp_size, stamp_size, scale=pixel_scale) + final_int.drawImage(bandpass, image=image3) + int_flux = image3.array.sum() + # Be *slightly* less stringent in this test given that we did use interpolation. + printval(image, image3) + np.testing.assert_almost_equal( + int_flux/analytic_flux, 1.0, 3, + err_msg="Drawn ChromaticConvolve flux (interpolated) doesn't match analytic prediction") + # As an aside, check for appropriate tests of 'integrator' argument. + assert_raises(ValueError, final_int.drawImage, bandpass, integrator='midp') # minor misspelling + assert_raises(TypeError, final_int.drawImage, bandpass, integrator=galsim.integ.midpt) + do_pickle(PSF) # Go back to no interpolation (this will effect the PSFs that are used below). PSF = PSF.deinterpolated @@ -610,6 +639,7 @@ def test_chromatic_flux(): np.testing.assert_almost_equal(image.array.sum(), 1.0, 4, err_msg="Drawn ChromaticConvolve flux doesn't match " "using ChromaticObject.withMagnitude(0.0)") + assert_raises(galsim.GalSimError, star.withMagnitude, 25.0, bandpass) # Some very simple tests of withFluxDensity. star7 = star.withFluxDensity(5.0, 500) @@ -673,6 +703,11 @@ def test_ChromaticConvolution_of_ChromaticConvolution(): if any(isinstance(h, galsim.ChromaticConvolution) for h in g.obj_list): raise AssertionError("ChromaticConvolution did not expand ChromaticConvolution argument") + assert_raises(TypeError, galsim.ChromaticConvolution) + assert_raises(TypeError, galsim.ChromaticConvolution, bulge_SED) + assert_raises(TypeError, galsim.ChromaticConvolution, [a,b], invalid=True) + assert_raises(NotImplementedError, galsim.ChromaticConvolution, [a,b], real_space=True) + @timer def test_ChromaticAutoConvolution(): @@ -1043,6 +1078,11 @@ def test_ChromaticObject_shear(): np.testing.assert_almost_equal(mom['Myy'] / (sigma/pixel_scale)**2, sh_myy, decimal=4) np.testing.assert_almost_equal(mom['Mxy'] / (sigma/pixel_scale)**2, sh_mxy, decimal=4) + assert_raises(TypeError, cgal.shear, 0.1, 0.3) + assert_raises(TypeError, cgal.shear, 0.1) + assert_raises(TypeError, cgal.shear, shear, g1=0.1, g2=0.2) + assert_raises(TypeError, cgal.shear, shear=shear, g1=0.1, g2=0.2) + @timer def test_ChromaticObject_shift(): @@ -1070,39 +1110,63 @@ def test_ChromaticObject_shift(): flux2, 2.*flux, 5, err_msg="rotated ChromaticObject * 2 resulted in wrong flux.") + cgal = galsim.Gaussian(fwhm=1.0) * bulge_SED + assert_raises(TypeError, cgal.shift) + assert_raises(TypeError, cgal.shift, 0.1) + assert_raises(TypeError, cgal.shift, shift, 0.1, 0.2) + assert_raises(TypeError, cgal.shift, shift, dx=0.1, dy=0.2) + assert_raises(TypeError, cgal.shift, shift=shift) @timer def test_ChromaticObject_compound_affine_transformation(): """ Check that making a (separable) object chromatic before a bunch of transformations is equivalent to making it chromatic after a bunch of transformations. """ - im1 = galsim.ImageD(32, 32, scale=0.2) - im2 = galsim.ImageD(32, 32, scale=0.2) shear = galsim.Shear(eta=1.0, beta=0.3*galsim.radians) scale = 1.1 theta = 0.1 * galsim.radians shift = (0.1, 0.3) + sed = galsim.SED('wave**0.3', 'nm', 'fphotons') + bandpass = galsim.Bandpass('1', 'nm', blue_limit=400, red_limit=550) a = galsim.Gaussian(fwhm=1.0) a = a.shear(shear).shift(shift).rotate(theta).dilate(scale) a = a.shear(shear).shift(shift).rotate(theta).expand(scale) a = a.lens(g1=0.1, g2=0.1, mu=1.1).shift(shift).rotate(theta).magnify(scale) - a = a * bulge_SED + a = a * sed - b = galsim.Gaussian(fwhm=1.0) * bulge_SED + b = galsim.Gaussian(fwhm=1.0) * sed b = b.shear(shear).shift(shift).rotate(theta).dilate(scale) b = b.shear(shear).shift(shift).rotate(theta).expand(scale) b = b.lens(g1=0.1, g2=0.1, mu=1.1).shift(shift).rotate(theta).magnify(scale) - a.drawImage(bandpass, image=im1, method='no_pixel') - b.drawImage(bandpass, image=im2, method='no_pixel') + # Include a few gratuitous combinations of functional and static values. + pshift = galsim.PositionD(*shift) + c = galsim.Gaussian(fwhm=1.0) * sed + c = c.shear(shear).shift(lambda w: shift).rotate(theta).dilate(lambda w: scale) + c = c.shear(shear).shift(lambda w: pshift).rotate(theta).expand(scale) + c = c.lens(g1=lambda w:0.1, g2=0.1, mu=lambda w:1.1).shift(shift).rotate(theta).magnify(scale) + + d = galsim.Gaussian(fwhm=1.0) * sed + d = d.shear(lambda w: shear).shift(pshift).rotate(lambda w: theta).dilate(scale) + d = d.shear(shear).shift(shift).rotate(theta).transform(scale, lambda w:0, lambda w:0, scale) + d = d.lens(g1=0.1, g2=lambda w:0.1, mu=1.1).shift(shift).rotate(theta).magnify(scale) + + im1 = galsim.ImageD(32, 32, scale=0.2) + im1 = a.drawImage(bandpass, image=im1.copy(), method='no_pixel') + im2 = b.drawImage(bandpass, image=im1.copy(), method='no_pixel') + im3 = c.drawImage(bandpass, image=im1.copy(), method='no_pixel') + im4 = d.drawImage(bandpass, image=im1.copy(), method='no_pixel') printval(im1, im2) - np.testing.assert_array_almost_equal(im1.array, im2.array, 5, + np.testing.assert_array_almost_equal(im2.array, im1.array, 5, + "ChromaticObject affine transformation not equal to " + "GSObject affine transformation") + np.testing.assert_array_almost_equal(im3.array, im1.array, 5, + "ChromaticObject affine transformation not equal to " + "GSObject affine transformation") + np.testing.assert_array_almost_equal(im4.array, im1.array, 5, "ChromaticObject affine transformation not equal to " "GSObject affine transformation") - - do_pickle(a) - do_pickle(b) # Check flux scaling flux = im2.array.sum() @@ -1112,6 +1176,13 @@ def test_ChromaticObject_compound_affine_transformation(): flux2, 2.*flux, 5, err_msg="transformed ChromaticObject * 2 resulted in wrong flux.") + # Just check that the cache resizing routines are what the docs say they are. + galsim.ChromaticObject.resize_multiplier_cache(100) + galsim.ChromaticConvolution.resize_effective_prof_cache(100) + + # Check some branches in repr that we wouldn't hit otherwise. + repr(a); repr(b); repr(c); repr(d) + @timer def test_analytic_integrator(): @@ -1301,6 +1372,13 @@ def test_separable_ChromaticSum(): np.testing.assert_array_almost_equal(img1.array, img4.array, 5, "separable ChromaticSum not correctly drawn") + assert_raises(TypeError, galsim.ChromaticSum, + [mono_gal1 * bulge_SED, mono_gal2 * bulge_SED], invalid=3) + assert_raises(TypeError, galsim.ChromaticSum) + assert_raises(TypeError, galsim.ChromaticSum, bulge_SED) + with assert_raises(galsim.GalSimIncompatibleValuesError): + sum = mono_gal1 * bulge_SED + mono_gal2 + @timer def test_centroid(): @@ -1551,26 +1629,22 @@ def __repr__(self): interp_psf = exact_psf.interpolate(waves, oversample_fac=oversample_fac) trans_exact_psf = \ exact_psf.shear(shear=chrom_shear).shift(dx=0.,dy=chrom_shift_y).dilate(chrom_dilate) - # The object is going to emit a warning that we don't want to worry about (it's good for code - # users, but a nuisance when testing), so let's deliberately ignore it by going into a - # `catch_warnings` context. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - trans_interp_psf = \ - interp_psf.shear(shear=chrom_shear).shift(dx=0.,dy=chrom_shift_y).dilate(chrom_dilate) - exact_obj = galsim.Convolve(star, trans_exact_psf) - interp_obj = galsim.Convolve(star, trans_interp_psf) - im_exact = exact_obj.drawImage(bandpass, scale=atm_scale) - im_interp = im_exact.copy() - im_interp = interp_obj.drawImage(bandpass, image=im_interp, scale=atm_scale) - # Note: since the image rendering should have been done in exactly the same way (it should - # have trashed the interpolation entirely), test to high precision. - np.testing.assert_array_almost_equal( - im_interp.array, im_exact.array, decimal=9, - err_msg='Did not do exact chromatic transformation by discarding interpolation') - # Also make sure that it ditched the interpolation. - assert not hasattr(trans_interp_psf, 'waves') + + with assert_warns(galsim.GalSimWarning): + trans_interp_psf = interp_psf.shear( + shear=chrom_shear).shift(dx=0.,dy=chrom_shift_y).dilate(chrom_dilate) + exact_obj = galsim.Convolve(star, trans_exact_psf) + interp_obj = galsim.Convolve(star, trans_interp_psf) + im_exact = exact_obj.drawImage(bandpass, scale=atm_scale) + im_interp = im_exact.copy() + im_interp = interp_obj.drawImage(bandpass, image=im_interp, scale=atm_scale) + # Note: since the image rendering should have been done in exactly the same way (it should + # have trashed the interpolation entirely), test to high precision. + np.testing.assert_array_almost_equal( + im_interp.array, im_exact.array, decimal=9, + err_msg='Did not do exact chromatic transformation by discarding interpolation') + # Also make sure that it ditched the interpolation. + assert not hasattr(trans_interp_psf, 'waves') @timer @@ -1616,6 +1690,9 @@ def test_ChromaticOpticalPSF(): obscuration=obscuration, nstruts=nstruts) do_pickle(psf) + with assert_raises(galsim.GalSimIncompatibleValuesError): + galsim.ChromaticOpticalPSF(lam=lam, diam=diam, aberrations=aberrations, lam_over_diam=0.02) + if not os.path.isfile(os.path.join(refdir, 'r_exact.fits')): import warnings warnings.warn("Could not find file r_exact.fits, so generating it from scratch. This " @@ -1643,8 +1720,10 @@ def test_ChromaticOpticalPSF(): obj = galsim.Convolve(star, psf) if __name__ == '__main__': - # This is slow, but it worth testing the pickling of InterpolatedChromaticObjects. + # This is slow, but it is worth testing the pickling of InterpolatedChromaticObjects. do_pickle(psf) + else: + repr(psf) im_r_ref = galsim.fits.read(os.path.join(refdir, 'r_exact.fits')) im_r = im_r_ref.copy() @@ -1741,6 +1820,9 @@ def test_ChromaticAiry(): im_r_2.array, im_r.array, decimal=8, err_msg='Inconsistent ChromaticAiry image when initializing a different way') + with assert_raises(galsim.GalSimIncompatibleValuesError): + galsim.ChromaticAiry(lam=lam, diam=diam, lam_over_diam=lam_over_diam/galsim.arcsec) + # Also check evaluation at a single wavelength. chromatic_psf_400 = psf.evaluateAtWavelength(400.) new_lam_over_diam = (1.e-9*400/diam)*galsim.radians @@ -1788,6 +1870,13 @@ def test_chromatic_fiducial_wavelength(): assert np.isfinite(img1.array.sum()), "drawImage failed to identify fiducial wavelength" assert np.isfinite(img2.array.sum()), "drawImage failed to identify fiducial wavelength" + # Pathalogical sed that is zero across the band. + bad_sed = galsim.SED(galsim.LookupTable([300,498,499,601,602,800], + [ 1, 1, 0, 0, 1, 1], 'linear'), 'nm', 'flambda') + gal3 = galsim.Gaussian(fwhm=1) * bad_sed + with assert_raises(galsim.GalSimError): + gal3.drawImage(bp) + @timer def test_chromatic_image_setup(): @@ -1839,6 +1928,79 @@ def test_convolution_of_spectral(): assert_raises(ValueError, galsim.Convolve, cgal1, cgal1, cgal2, cgal3) +def check_chromatic_invariant(obj, bps=None, waves=None): + """ Helper function to check that ChromaticObjects satisfy intended invariants. + """ + if bps is None: + # load a filter + bppath = os.path.join(galsim.meta_data.share_dir, 'bandpasses') + bandpass = (galsim.Bandpass(os.path.join(bppath, 'LSST_r.dat'), 'nm') + .truncate(relative_throughput=1e-3) + .thin(rel_err=1e-3)) + bps = [bandpass] + + if waves is None: + waves = [500.] + + assert isinstance(obj.wave_list, np.ndarray) + assert isinstance(obj.separable, bool) + assert isinstance(obj.interpolated, bool) + assert isinstance(obj.deinterpolated, (galsim.ChromaticObject, galsim.GSObject)) + + for wave in waves: + desired = obj.SED(wave) + # Since InterpolatedChromaticObject.evaluateAtWavelength involves actually drawing an + # image, which implies flux can be lost off of the edges of the image, we don't expect + # its accuracy to be nearly as good as for other objects. + decimal = 2 if obj.interpolated else 7 + np.testing.assert_almost_equal(obj.evaluateAtWavelength(wave).flux, desired, + decimal) + # Don't bother trying to draw a deconvolution. + if isinstance(obj, galsim.ChromaticDeconvolution): + continue + np.testing.assert_allclose( + obj.evaluateAtWavelength(wave).drawImage().array.sum(dtype=float), + desired, + rtol=1e-2) + + if obj.SED.spectral: + for bp in bps: + calc_flux = obj.calculateFlux(bp) + np.testing.assert_equal(obj.SED.calculateFlux(bp), calc_flux) + np.testing.assert_allclose(calc_flux, + obj.drawImage(bp).array.sum(dtype=float), rtol=1e-2) + # Also try manipulating exptime and area. + np.testing.assert_allclose( + calc_flux * 10, + obj.drawImage(bp, exptime=5, area=2).array.sum(dtype=float), rtol=1e-2) + + assert_raises(galsim.GalSimSEDError, galsim.Deconvolve, obj) + assert_raises(galsim.GalSimSEDError, galsim.AutoConvolve, obj) + assert_raises(galsim.GalSimSEDError, galsim.AutoCorrelate, obj) + assert_raises(galsim.GalSimSEDError, galsim.FourierSqrt, obj) + + try: + obj = copy.copy(obj) + obj.SED = galsim.SED('1', 'nm', '1') + except AttributeError: + return + if isinstance(obj, galsim.GSObject): return + + # Test errors for dimensionless SEDs. + with assert_raises(galsim.GalSimSEDError): + obj.drawImage(bps[0]) + with assert_raises(galsim.GalSimSEDError): + obj.drawKImage(bps[0]) + with assert_raises(galsim.GalSimSEDError): + obj.withFluxDensity(100., 500) + with assert_raises(galsim.GalSimSEDError): + obj.withFluxDensity(100., 500) + with assert_raises(galsim.GalSimSEDError): + obj.calculateFlux(bps[0]) + with assert_raises(galsim.GalSimSEDError): + obj.calculateMagnitude(bps[0]) + + @timer def test_chromatic_invariant(): # Test atomic and non-transformed objects first. @@ -1857,6 +2019,9 @@ def test_chromatic_invariant(): do_pickle(chrom3) do_pickle(galsim.ChromaticObject(gsobj)) + with assert_raises(TypeError): + galsim.ChromaticObject(bulge_SED) + check_chromatic_invariant(chrom1) check_chromatic_invariant(chrom2) check_chromatic_invariant(chrom3) @@ -1876,22 +2041,30 @@ def test_chromatic_invariant(): np.testing.assert_almost_equal(img1.array, img3.array, decimal=5) # ChromaticAtmosphere - chrom_atm = galsim.ChromaticAtmosphere(gsobj, 500.0, zenith_angle=20.0 * galsim.degrees) + chrom_atm = galsim.ChromaticAtmosphere(gsobj, 500.0, zenith_angle=20.0 * galsim.degrees, + pressure=70., temperature=285., H2O_pressure=1.05) check_chromatic_invariant(chrom_atm) do_pickle(chrom_atm) + assert_raises(TypeError, galsim.ChromaticAtmosphere, gsobj, + 500.0, zenith_angle=20.0 * galsim.degrees, invalid=3) + # ChromaticTransformation formed from __mul__ chrom = gsobj * bulge_SED check_chromatic_invariant(chrom) do_pickle(chrom) + with assert_raises(galsim.GalSimError): + chrom.noise + # ChromaticOpticalPSF - chrom_opt = galsim.ChromaticOpticalPSF(lam=500.0, diam=2.0, tip=2.0, tilt=3.0, defocus=0.2) + chrom_opt = galsim.ChromaticOpticalPSF(lam=500.0, diam=2.0, tip=2.0, tilt=3.0, defocus=0.2, + scale_unit='arcmin') check_chromatic_invariant(chrom_opt) do_pickle(chrom_opt) # ChromaticAiry - chrom_airy = galsim.ChromaticAiry(lam=500.0, diam=3.0) + chrom_airy = galsim.ChromaticAiry(lam=500.0, diam=3.0, scale_unit=galsim.arcmin) check_chromatic_invariant(chrom_airy) do_pickle(chrom_airy) @@ -1904,6 +2077,8 @@ def test_chromatic_invariant(): # e.g. autoconv2 has no hope. But there are a few do_pickle calls that are commented # out that we should probably try to make work. A job for another day, though... #do_pickle(chrom_sum_noSED) + repr(chrom_sum_noSED) + str(chrom_sum_noSED) chrom_sum_SED = chrom + chrom # also separable check_chromatic_invariant(chrom_sum_SED) @@ -1922,6 +2097,9 @@ def test_chromatic_invariant(): check_chromatic_invariant(conv1) do_pickle(conv1) + with assert_raises(galsim.GalSimError): + conv1.noise + conv2 = galsim.Convolve(chrom_airy, chrom_opt) # Non-SEDed check_chromatic_invariant(conv2) do_pickle(conv2) @@ -2002,6 +2180,7 @@ def test_ne(): gal2 = galsim.Gaussian(fwhm=1.1) cgal1 = galsim.ChromaticObject(gal1).dilate(lambda w:1) cgal2 = galsim.ChromaticObject(gal2).dilate(lambda w:1) + cgal3 = cgal1.interpolate(np.arange(400, 550, 10)) # ChromaticObject. Only param is the GSObject to chromaticize. # The following should test unequal: @@ -2017,6 +2196,8 @@ def test_ne(): # oversample_fac. # Also get a copy of cgal1 and make it interpolatable, but with a different waves argument. gals = [cgal1, + cgal2, + cgal3, galsim.InterpolatedChromaticObject(cgal1, np.arange(500, 700, 50)), galsim.InterpolatedChromaticObject(cgal2, np.arange(500, 700, 50)), galsim.InterpolatedChromaticObject(cgal1, np.arange(500, 700, 25)), @@ -2062,24 +2243,40 @@ def test_ne(): sed2 = galsim.SED(lambda w: 2*w, 'nm', 'flambda') # The following should test unequal. gals = [gal1 * sed1, - gal2 * sed2, - gal1 * sed2] + gal1 * sed2, + gal2 * sed1, + gal2 * sed2] all_obj_diff(gals) # ChromaticTransformation. Params are an object (possibly chromatic), a jacobian jac, an # offset, a flux_ratio, and gsparams. For coverage, test jac, offset, and flux_ratio as # consts and functions. - jac = lambda w: [[w, 0], [0, 1]] - offset = lambda w: (0, w) - flux_ratio = lambda w: w + jac1 = lambda w: [[w, 0], [0, 1]] + jac2 = lambda w: [[w, 0], [0, w]] + offset1 = lambda w: (0, w) + offset2 = lambda w: (w, 0) + flux_ratio1 = lambda w: w + flux_ratio2 = lambda w: w**2 # The following should test unequal. + with assert_warns(galsim.GalSimWarning): + trans_cgal3 = galsim.ChromaticTransformation( + cgal3, jac=jac1, offset=offset1, flux_ratio=flux_ratio1), gals = [galsim.ChromaticTransformation(cgal1), - galsim.ChromaticTransformation(cgal1, jac=[[1, 1.1], [0.1, 1]]), - galsim.ChromaticTransformation(cgal1, jac=jac), - galsim.ChromaticTransformation(cgal1, offset=(0.1, 0.0)), - galsim.ChromaticTransformation(cgal1, offset=offset), - galsim.ChromaticTransformation(cgal1, flux_ratio=1.1), - galsim.ChromaticTransformation(cgal1, flux_ratio=flux_ratio), + galsim.ChromaticTransformation(cgal3), + galsim.ChromaticTransformation(gal1, jac=[[1, 1.1], [0.1, 1]]), + galsim.ChromaticTransformation(gal1, jac=[[1, 0.1], [0.1, 1]]), + galsim.ChromaticTransformation(gal1, jac=jac1), + galsim.ChromaticTransformation(gal1, jac=jac2), + galsim.ChromaticTransformation(gal1, offset=(0.1, 0.0)), + galsim.ChromaticTransformation(gal1, offset=(0.0, 0.1)), + galsim.ChromaticTransformation(gal1, offset=offset1), + galsim.ChromaticTransformation(gal1, offset=offset2), + galsim.ChromaticTransformation(gal1, flux_ratio=1.1), + galsim.ChromaticTransformation(gal1, flux_ratio=1.4), + galsim.ChromaticTransformation(gal1, flux_ratio=flux_ratio1), + galsim.ChromaticTransformation(gal1, flux_ratio=flux_ratio2), + galsim.ChromaticTransformation(cgal1, jac=jac1, offset=offset1, flux_ratio=flux_ratio1), + trans_cgal3, galsim.ChromaticTransformation(cgal1, gsparams=gsp)] all_obj_diff(gals) @@ -2087,6 +2284,7 @@ def test_ne(): # The following should test unequal. gals = [galsim.ChromaticSum(cgal1), galsim.ChromaticSum(cgal1, cgal2), + galsim.ChromaticSum(cgal3, cgal2), galsim.ChromaticSum(cgal2, cgal1), # Not! commutative. galsim.ChromaticSum(galsim.ChromaticSum(cgal1, cgal2), cgal2), galsim.ChromaticSum(cgal1, galsim.ChromaticSum(cgal2, cgal2)), # Not! associative. @@ -2095,8 +2293,11 @@ def test_ne(): # ChromaticConvolution. Params are objs to convolve and potentially gsparams. # The following should test unequal + with assert_warns(galsim.GalSimWarning): + conv_32 = galsim.ChromaticConvolution(cgal3, cgal2), gals = [galsim.ChromaticConvolution(cgal1), galsim.ChromaticConvolution(cgal1, cgal2), + conv_32, galsim.ChromaticConvolution(cgal2, cgal1), # Not! commutative. galsim.ChromaticConvolution(galsim.ChromaticConvolution(cgal1, cgal2), cgal2), # ChromaticConvolution is associative! (unlike galsim.Convolution) @@ -2107,18 +2308,21 @@ def test_ne(): # ChromaticDeconvolution. Only params here are obj to deconvolve and gsparams. gals = [galsim.ChromaticDeconvolution(cgal1), galsim.ChromaticDeconvolution(cgal2), + galsim.ChromaticDeconvolution(cgal3), galsim.ChromaticDeconvolution(cgal1, gsparams=gsp)] all_obj_diff(gals) # ChromaticAutoConvolution. Only params here are obj to deconvolve and gsparams. gals = [galsim.ChromaticAutoConvolution(cgal1), galsim.ChromaticAutoConvolution(cgal2), + galsim.ChromaticAutoConvolution(cgal3), galsim.ChromaticAutoConvolution(cgal1, gsparams=gsp)] all_obj_diff(gals) # ChromaticAutoCorrelation. Only params here are obj to deconvolve and gsparams. gals = [galsim.ChromaticAutoCorrelation(cgal1), galsim.ChromaticAutoCorrelation(cgal2), + galsim.ChromaticAutoCorrelation(cgal3), galsim.ChromaticAutoCorrelation(cgal1, gsparams=gsp)] all_obj_diff(gals) diff --git a/tests/test_integ.py b/tests/test_integ.py index 4a2775fdc1a..ebb735f4b13 100644 --- a/tests/test_integ.py +++ b/tests/test_integ.py @@ -234,6 +234,10 @@ def test_trapz_basic(): result/expected_val, 1.0, decimal=6, verbose=True, err_msg='Test of trapzRule() with points failed for f(x)=x^2 from 0 to 1') + assert_raises(ValueError, galsim.integ.trapz, func, 0, 1, points=np.linspace(0, 1.1, 100)) + assert_raises(ValueError, galsim.integ.trapz, func, 0.1, 1, points=np.linspace(0, 1, 100)) + assert_raises(TypeError, galsim.integ.trapz, func, 0.1, 1, points=2.3) + if __name__ == "__main__": test_gaussian_finite_limits() diff --git a/tests/test_photon_array.py b/tests/test_photon_array.py index 8e5e43a001b..3e4478b06c5 100644 --- a/tests/test_photon_array.py +++ b/tests/test_photon_array.py @@ -499,19 +499,40 @@ def test_dcr(): err_msg="PhotonDCR with alpha=0 didn't match") # Also check invalid parameters + zenith_coord = galsim.CelestialCoord(13.54 * galsim.hours, lsst_lat) assert_raises(TypeError, galsim.PhotonDCR, zenith_angle=zenith_angle, parallactic_angle=parallactic_angle) # base_wavelength is required assert_raises(TypeError, galsim.PhotonDCR, base_wavelength=500, parallactic_angle=parallactic_angle) # zenith_angle (somehow) is required - assert_raises(TypeError, galsim.PhotonDCR, - base_wavelength=500, + assert_raises(TypeError, galsim.PhotonDCR, 500, + zenith_angle=34.4, + parallactic_angle=parallactic_angle) # zenith_angle must be Angle + assert_raises(TypeError, galsim.PhotonDCR, 500, + zenith_angle=zenith_angle, + parallactic_angle=34.5) # parallactic_angle must be Angle + assert_raises(TypeError, galsim.PhotonDCR, 500, + obj_coord=obj_coord, + latitude=lsst_lat) # Missing HA + assert_raises(TypeError, galsim.PhotonDCR, 500, + obj_coord=obj_coord, + HA=local_sidereal_time-obj_coord.ra) # Missing latitude + assert_raises(TypeError, galsim.PhotonDCR, 500, + obj_coord=obj_coord) # Need either zenith_coord, or (HA,lat) + assert_raises(TypeError, galsim.PhotonDCR, 500, + obj_coord=obj_coord, + zenith_coord=zenith_coord, + HA=local_sidereal_time-obj_coord.ra) # Can't have both HA and zenith_coord + assert_raises(TypeError, galsim.PhotonDCR, 500, + obj_coord=obj_coord, + zenith_coord=zenith_coord, + latitude=lsst_lat) # Can't have both lat and zenith_coord + assert_raises(TypeError, galsim.PhotonDCR, 500, zenith_angle=zenith_angle, parallactic_angle=parallactic_angle, H20_pressure=1.) # invalid (misspelled) - assert_raises(ValueError, galsim.PhotonDCR, - base_wavelength=500, + assert_raises(ValueError, galsim.PhotonDCR, 500, zenith_angle=zenith_angle, parallactic_angle=parallactic_angle, scale_unit='inches') # invalid scale_unit diff --git a/tests/test_real.py b/tests/test_real.py index a2b76b17b73..9a5b8debbc5 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -687,6 +687,9 @@ def check_crg_noise(n_sed, n_im, n_trial, tol): print("Convolving by output PSF") objs = [galsim.Convolve(crg, out_PSF) for crg in crgs] + with assert_raises(galsim.GalSimError): + noise = objs[0].noise # Invalid before drawImage is called + print("Drawing through output filter") out_imgs = [obj.drawImage(visband, nx=30, ny=30, scale=0.1) for obj in objs] diff --git a/tests/test_sed.py b/tests/test_sed.py index 08d511bb5c5..b6512094f37 100644 --- a/tests/test_sed.py +++ b/tests/test_sed.py @@ -184,10 +184,27 @@ def test_SED_add(): err_msg="Wrong sum in SED.__add__") np.testing.assert_almost_equal(c.redshift, a.redshift, 10, err_msg="Wrong redshift in SED sum") + # Adding together two SEDs with different redshifts should fail. d = b.atRedshift(0.1) - with assert_raises(ValueError): - b.__add__(d) + with assert_raises(galsim.GalSimIncompatibleValuesError): + b + d + with assert_raises(galsim.GalSimIncompatibleValuesError): + d + b + + # Can't add incompatible spectral types + a = a.atRedshift(0) + b = a.atRedshift(0) + c = galsim.SED(2.0, 'nm', '1') + with assert_raises(galsim.GalSimIncompatibleValuesError): + a + c + with assert_raises(galsim.GalSimIncompatibleValuesError): + c + a + with assert_raises(galsim.GalSimIncompatibleValuesError): + b + c + with assert_raises(galsim.GalSimIncompatibleValuesError): + c + b + @timer @@ -326,6 +343,16 @@ def test_SED_div(): np.testing.assert_almost_equal(d(x), a(x)/4.2/2/e(x), 10, err_msg="Found wrong value in SED.__div__") + # Can't divide by spectral SED + with assert_raises(galsim.GalSimSEDError): + a0_lt / a0_fn + with assert_raises(galsim.GalSimSEDError): + a0_fn / a0_lt + with assert_raises(galsim.GalSimSEDError): + e / a0_lt + with assert_raises(galsim.GalSimSEDError): + e / a0_fn + @timer def test_SED_atRedshift(): @@ -381,6 +408,16 @@ def __init__(self, wave_list): with assert_raises(galsim.GalSimError): galsim.utilities.combine_wave_list(a, d) + # Degenerate case works. + sed = galsim.SED('CWW_Scd_ext.sed', wave_type='nm', flux_type='flambda') + wave_list, blue_limit, red_limit = galsim.utilities.combine_wave_list(sed) + np.testing.assert_equal(wave_list, sed.wave_list) + np.testing.assert_equal(blue_limit, sed.blue_limit) + np.testing.assert_equal(red_limit, sed.red_limit) + + # Doesn't know about our A class though. + assert_raises(TypeError, galsim.utilities.combine_wave_list, a) + @timer def test_SED_roundoff_guard(): @@ -466,25 +503,40 @@ def test_SED_withFlux(): for fast in [True, False]: a = galsim.SED(os.path.join(sedpath, 'CWW_E_ext.sed'), wave_type='ang', flux_type='flambda', fast=fast) + b = galsim.SED('wave', wave_type='nm', flux_type='fphotons') if z != 0: a = a.atRedshift(z) + b = b.atRedshift(z) a = a.withFlux(1.0, rband) + b = b.withFlux(1.0, rband) np.testing.assert_array_almost_equal(a.calculateFlux(rband), 1.0, 5, "Setting SED flux failed.") + np.testing.assert_array_almost_equal(b.calculateFlux(rband), 1.0, 5, + "Setting SED flux failed.") # Should be equivalent to multiplying an SED * Bandpass and computing the # "bolometric" flux. ab = a * rband + bb = b * rband bolo_bp = galsim.Bandpass('1', blue_limit=ab.blue_limit, red_limit=ab.red_limit, wave_type='nm') np.testing.assert_array_almost_equal(ab.calculateFlux(bolo_bp), 1.0, 5, "Calculating SED flux from sed * bp failed.") + np.testing.assert_array_almost_equal(bb.calculateFlux(bolo_bp), 1.0, 5, + "Calculating SED flux from sed * bp failed.") # Multiplying in the other order also works. ba = rband * a np.testing.assert_array_almost_equal(ba.calculateFlux(bolo_bp), 1.0, 5, "Calculating SED flux from sed * bp failed.") + # Invalid for dimensionless SED + c = galsim.SED(2.0, 'nm', '1') + with assert_raises(galsim.GalSimSEDError): + c.withFlux(1.0, rband) + with assert_raises(galsim.GalSimSEDError): + c.calculateFlux(rband) + @timer def test_SED_withFluxDensity(): @@ -505,6 +557,11 @@ def test_SED_withFluxDensity(): np.testing.assert_array_almost_equal( a(500), 3.0, 5, "Setting SED flux density failed.") + # Invalid for dimensionless SED + c = galsim.SED(2.0, 'nm', '1') + with assert_raises(galsim.GalSimSEDError): + c.withFluxDensity(1.0, 500) + @timer def test_SED_calculateMagnitude(): @@ -566,6 +623,18 @@ def test_SED_calculateMagnitude(): thresh = 0.3 if filter_name == 'u' else 0.1 assert (abs((AB_mag - vega_mag) - conversion) < thresh) + # Invalid for dimensionless SED + c = galsim.SED(2.0, 'nm', '1') + with assert_raises(galsim.GalSimSEDError): + c.withMagnitude(24.0, bandpass) + + # Zeropoint needs to be set. + bp = galsim.Bandpass(galsim.LookupTable([1,2,3,4,5], [1,2,3,4,5]), 'nm') + with assert_raises(galsim.GalSimError): + sed.withMagnitude(24.0, bp) + with assert_raises(galsim.GalSimError): + sed.calculateMagnitude(bp) + @timer def test_redshift_calculateFlux(): @@ -582,6 +651,16 @@ def test_redshift_calculateFlux(): else: print('z = {} flux = {}'.format(z, sedz.calculateFlux(bp))) + # All analytic has easy to check answers + sed = galsim.SED('(wave/500)**2', wave_type='nm', flux_type='fphotons') + bp = galsim.Bandpass('1', blue_limit=500, red_limit=1000, wave_type='nm') + + for z in [0, 0.19, 0.2, 0.21, 2.5, 2.99, 3, 3.01, 4]: + sedz = sed.atRedshift(z) + f = sedz.calculateFlux(bp) + print('z = {} flux = {}'.format(z, f)) + np.testing.assert_almost_equal(f, 7./3. * 500 / (1.+z)**2) + @timer def test_SED_calculateDCRMomentShifts(): @@ -855,6 +934,41 @@ def test_thin(): print("realized error = ",(flux-thin_flux)/flux) assert np.abs(thin_err) < err, "Thinned SED failed accuracy goal, w/ range shrinkage." + assert_raises(ValueError, s.thin, rel_err=-0.5) + assert_raises(ValueError, s.thin, rel_err=1.5) + # These errors aren't accessible from the SED or Bandpass calls. + assert_raises(ValueError, galsim.utilities.thin_tabulated_values, + s.wave_list[3:], s._spec.getVals()) + assert_raises(ValueError, galsim.utilities.thin_tabulated_values, + s.wave_list[-1::-1], s._spec.getVals()) + + # Check some pathalogical spectra to stress the thinning algorithm + s = galsim.SED(galsim.LookupTable(range(6), [0,0,1,1,0,0]),'nm','1').thin() + print('s = ',s) + np.testing.assert_equal(s.wave_list, range(1,5)) + + s = galsim.SED(galsim.LookupTable(range(6), [0,0,1,1,0,0]),'nm','1').thin(trim_zeros=False) + print('s = ',s) + np.testing.assert_equal(s.wave_list, range(6)) + + s = galsim.SED(galsim.LookupTable(range(8), [1.e-8,1.e-6,1,1,1,1.e-6,1.e-10,1.e-100]), + 'nm','1').thin(preserve_range=False) + print('s = ',s) + np.testing.assert_equal(s.wave_list, range(1,6)) + + s = galsim.SED(galsim.LookupTable(range(8), np.zeros(8)),'nm','1').thin() + print('s = ',s) + np.testing.assert_equal(s.wave_list, [0,7]) + + s = galsim.SED(galsim.LookupTable(range(2), [1,1], interpolant='linear'),'nm','1').thin() + print('s = ',s) + np.testing.assert_equal(s.wave_list, [0,1]) + + s = galsim.SED(galsim.LookupTable(range(3), [1, 1.e-20, 0], interpolant='linear'), + 'nm','1').thin(preserve_range=False) + print('s = ',s) + np.testing.assert_equal(s.wave_list, [0,1]) + if __name__ == "__main__": test_SED_basic() diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 5ab6dc67889..55f3575d840 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -768,6 +768,13 @@ def test_python_LRU_Cache(): assert cache(i) == f(i) assert (1,) not in cache.cache + # "Resize" to same size does nothing. + cache.resize(newsize) + assert len(cache.cache) == 20 + assert (1,) not in cache.cache + for i in range(2, newsize+2): + assert (i,) in cache.cache + # Test mostly non-destructive cache contraction. # Already bumped (0,) and (1,), so (2,) should be the first to get bumped for i in range(newsize-1, size, -1): @@ -775,6 +782,10 @@ def test_python_LRU_Cache(): cache.resize(i) assert (newsize - (i - 1),) not in cache.cache + assert_raises(ValueError, cache.resize, 0) + assert_raises(ValueError, cache.resize, -20) + + @timer def test_rand_with_replacement(): """Test routine to select random indices with replacement.""" From 3e09f6156846e275cf3fbb11a2caa5a77963107d Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 28 Apr 2018 18:15:35 -0400 Subject: [PATCH 49/96] Increase test coverage of gsobjects (#755) --- galsim/convolve.py | 15 ++--- galsim/gsobject.py | 48 ++++++++++----- galsim/inclined.py | 3 +- galsim/sersic.py | 3 + galsim/transform.py | 9 +-- galsim/utilities.py | 10 ++-- tests/test_calc.py | 2 + tests/test_chromatic.py | 8 ++- tests/test_config_gsobject.py | 34 ++++------- tests/test_convolve.py | 25 +++++--- tests/test_deprecated.py | 10 +--- tests/test_draw.py | 109 ++++++++++++++++++++++++++-------- tests/test_exponential.py | 2 + tests/test_fouriersqrt.py | 4 ++ tests/test_gaussian.py | 21 +++++++ tests/test_inclined.py | 6 ++ tests/test_kolmogorov.py | 1 + tests/test_moffat.py | 11 ++++ tests/test_photon_array.py | 3 + tests/test_sersic.py | 15 +++++ tests/test_shapelet.py | 12 ++++ tests/test_spergel.py | 6 ++ tests/test_transforms.py | 15 +++++ 23 files changed, 273 insertions(+), 99 deletions(-) diff --git a/galsim/convolve.py b/galsim/convolve.py index 58bfd615f14..28aec020dd3 100644 --- a/galsim/convolve.py +++ b/galsim/convolve.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject, ChromaticConvolution from .utilities import lazy_property, doc_inherit -from .errors import GalSimWarning +from .errors import GalSimWarning, GalSimError def Convolve(*args, **kwargs): """A function for convolving 2 or more GSObject or ChromaticObject instances. @@ -349,14 +349,10 @@ def _xValue(self, pos): elif len(self.obj_list) == 2: try: return self._sbp.xValue(pos._p) - except AttributeError: # pragma: no cover - # TODO: Once we have a GSObject subclass that doesn't implement the _sbp - # attribute, add a test that this branch works properly. - # (Currently it is unreachable, since all profiles have _sbp.) - raise NotImplementedError( + except (AttributeError, RuntimeError): + raise GalSimError( "At least one profile in %s does not implement real-space convolution"%self) else: - # XXX Not sure if this code is reachable... raise GalSimError("Cannot use real_space convolution for >2 profiles") @doc_inherit @@ -371,11 +367,10 @@ def _drawReal(self, image): elif len(self.obj_list) == 2: try: self._sbp.draw(image._image, image.scale) - except AttributeError: # pragma: no cover - raise NotImplementedError( + except (AttributeError, RuntimeError): + raise GalSimError( "At least one profile in %s does not implement real-space convolution"%self) else: - # XXX Not sure if this code is reachable... raise GalSimError("Cannot use real_space convolution for >2 profiles") @doc_inherit diff --git a/galsim/gsobject.py b/galsim/gsobject.py index c906f34c94c..dfccabe6d93 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -727,7 +727,7 @@ def kValue(self, *args, **kwargs): kpos = parse_pos_args(args,kwargs,'kx','ky') return self._kValue(kpos) - def _kValue(self, kpos): + def _kValue(self, kpos): # pragma: no cover (all our classes override this) """Equivalent to kValue(kpos), but kpos must be a galsim.PositionD instance. """ raise NotImplementedError("%s does not implement kValue"%self.__class__.__name__) @@ -873,6 +873,8 @@ def shear(self, *args, **kwargs): shear = args[0] elif len(args) > 1: raise TypeError("Error, too many unnamed arguments to GSObject.shear!") + elif len(kwargs) == 0: + raise TypeError("Error, shear argument is required") else: shear = Shear(**kwargs) return Transform(self, jac=shear.getMatrix().ravel().tolist()) @@ -1036,6 +1038,8 @@ def _setup_image(self, image, nx, ny, bounds, add_to_image, dtype, odd=False): if nx is not None or ny is not None: raise GalSimIncompatibleValuesError( "Cannot set both bounds and (nx, ny)", nx=nx, ny=ny, bounds=bounds) + if not bounds.isDefined(): + raise GalSimValueError("Cannot use undefined bounds", bounds) image = Image(bounds=bounds, dtype=dtype) elif nx is not None or ny is not None: if nx is None or ny is None: @@ -1499,7 +1503,7 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N # Check that image is sane if image is not None and not isinstance(image, Image): - raise GalSimValueError("image is not an Image instance", image) + raise TypeError("image is not an Image instance", image) # Make sure (gain, area, exptime) have valid values: if gain <= 0.: @@ -1981,7 +1985,7 @@ def _calculate_nphotons(self, n_photons, poisson_flux, max_extra_noise, rng): # Make n_photons an integer. iN = int(n_photons + 0.5) - if iN <= 0: # pragma: no cover + if iN <= 0: import warnings warnings.warn("Automatic n_photons calculation did not end up with positive N. " "(n_photons = {0}) No photons will be shot. " @@ -2116,15 +2120,10 @@ def drawPhot(self, image, gain=1., add_to_image=False, try: photons = self.shoot(thisN, ud) - except GalSimError: # pragma: no cover - # Give some extra explanation as a warning, then raise the original exception - # so the traceback shows as much detail as possible. - import warnings - warnings.warn( - "Unable to draw this GSObject with photon shooting. Perhaps it is a " - "Deconvolve or is a compound including one or more Deconvolve objects.", - GalSimWarning) - raise + except (GalSimError, NotImplementedError) as e: + raise GalSimError("Unable to draw this GSObject with photon shooting. Perhaps it " + "is a Deconvolve or is a compound including one or more " + "Deconvolve objects.\nOriginal error: %r"%(e)) if g != 1. or thisN != Ntot: photons.scaleFlux(g * thisN / Ntot) @@ -2224,8 +2223,12 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, from .wcs import PixelScale from .image import Image # Make sure provided image is complex - if image is not None and not image.iscomplex: - raise GalSimValueError("Provided image must be complex", image) + if image is not None: + if not isinstance(image, Image): + raise TypeError("Provided image must be galsim.Image", image) + + if not image.iscomplex: + raise GalSimValueError("Provided image must be complex", image) # Possibly get the scale from image. if image is not None and scale is None: @@ -2255,11 +2258,24 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, real_prof = PixelScale(dx).toImage(self) dtype = np.complex128 if image is None else image.dtype image = real_prof._setup_image(image, nx, ny, bounds, add_to_image, dtype, odd=True) + else: + # Do some checks that setup_image would have done for us + if bounds is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide bounds if image is provided", bounds=bounds, image=image) + if nx is not None or ny is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide nx,ny if image is provided", nx=nx, ny=ny, image=image) + if not image.bounds.isDefined(): + if add_to_image: + raise GalSimIncompatibleValuesError( + "Cannot add_to_image if image bounds are not defined", + add_to_image=add_to_image, image=image) # Can't both recenter a provided image and add to it. if recenter and image.center != PositionI(0,0) and add_to_image: raise GalSimIncompatibleValuesError( - "Cannot recenter a non-centered image when add_to_image=True", + "Cannot use add_to_image=True unless image is centered at (0,0) or recenter=False", recenter=recenter, image=image, add_to_image=add_to_image) # Set the center to 0,0 if appropriate @@ -2280,7 +2296,7 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, image += im2 return image - def _drawKImage(self, image): + def _drawKImage(self, image): # pragma: no cover (all our classes override this) """Equivalent to drawKImage(image, add_to_image, recenter=False, add_to_image=False), but without the normal sanity checks or the option to create the image automatically. diff --git a/galsim/inclined.py b/galsim/inclined.py index 86da92d0c9a..d7816587fa3 100644 --- a/galsim/inclined.py +++ b/galsim/inclined.py @@ -328,7 +328,8 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc self._r0 = self._hlr / _galsim.SersicHLR(self._n, 1.) else: if self._trunc <= math.sqrt(2.) * self._hlr: - raise GalSimRangerror("Sersic trunc must be > sqrt(2) * half_light_radius") + raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", + self._trunc, math.sqrt(2.) * self._hlr) self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) else: raise GalSimIncompatibleValuesError( diff --git a/galsim/sersic.py b/galsim/sersic.py index 60ada9505d1..dd06194ae78 100644 --- a/galsim/sersic.py +++ b/galsim/sersic.py @@ -216,6 +216,9 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, raise GalSimRangeError("Requested Sersic index is too large", self._n, Sersic._minimum_n, Sersic._maximum_n) + if self._trunc < 0: + raise GalSimRangeError("Sersic trunc must be > 0", self._trunc, 0.) + # Parse the radius options if half_light_radius is not None: if scale_radius is not None: diff --git a/galsim/transform.py b/galsim/transform.py index c99a3a56b26..b2cdfa46de4 100644 --- a/galsim/transform.py +++ b/galsim/transform.py @@ -162,11 +162,12 @@ def _flux(self): @lazy_property def _sbp(self): - if self._det == 0.: - raise GalSimError("Transformation has a degenerate Jacobian") dudx, dudy, dvdx, dvdy = self._jac.ravel() - return _galsim.SBTransform(self._original._sbp, dudx, dudy, dvdx, dvdy, - self._offset._p, self._flux_ratio, self.gsparams._gsp) + try: + return _galsim.SBTransform(self._original._sbp, dudx, dudy, dvdx, dvdy, + self._offset._p, self._flux_ratio, self.gsparams._gsp) + except RuntimeError as e: + raise GalSimError(str(e)) @lazy_property def _noise(self): diff --git a/galsim/utilities.py b/galsim/utilities.py index b47b9334d66..7a9fe3f844e 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -134,9 +134,11 @@ def canindex(arg): other_vals = [] if len(args) == 0: # Then name1,name2 need to be kwargs - # If not, then python will raise an appropriate error. - x = kwargs.pop(name1) - y = kwargs.pop(name2) + try: + x = kwargs.pop(name1) + y = kwargs.pop(name2) + except KeyError: + raise TypeError('Expecting kwargs %s, %s. Got %s'%(name1, name2, kwargs.keys())) elif ( ( isinstance(args[0], PositionI) or (not integer and isinstance(args[0], PositionD)) ) and len(args) <= 1+len(others) ): @@ -151,7 +153,7 @@ def canindex(arg): for arg in args[1:]: other_vals.append(arg) others.pop(0) - elif len(args) == 1: # pragma: no cover + elif len(args) == 1: if integer: raise TypeError("Cannot parse argument %s as a PositionI"%(args[0])) else: diff --git a/tests/test_calc.py b/tests/test_calc.py index 947345af9e3..06dce03f1d0 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -159,6 +159,8 @@ def test_sigma(): np.testing.assert_equal( (g1.sigma, g1.sigma), g1.calculateMomentRadius(rtype='both'), err_msg="Gaussian.calculateMomentRadius(both) returned wrong value.") + with assert_raises(galsim.GalSimValueError): + g1.calculateMomentRadius(rtype='invalid') # Check for a convolution of two Gaussians. Should be equivalent, but now will need to # do the calculation. diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 9e39e57fd09..28cce845acd 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -614,12 +614,17 @@ def test_chromatic_flux(): "using flux_ratio * ChromaticObject") # As should this. - star4 = star.withScaledFlux(flux_ratio) + star4 = star.withScaledFlux(lambda wave: flux_ratio) final = galsim.Convolve([star4, PSF]) final.drawImage(bandpass, image=image) np.testing.assert_almost_equal(image.array.sum()/target_flux, 1.0, 4, err_msg="Drawn ChromaticConvolve flux doesn't match " + "using ChromaticObject.withScaledFlux(flux_ratio)") + # Can't scale GSObject by function (just SED) + with assert_raises(TypeError): + galsim.Gaussian(fwhm=1e-8).withScaledFlux(lambda wave: flux) + with assert_raises(TypeError): + galsim.Gaussian(fwhm=1e-8) * (lambda wave: flux) # Test ChromaticObject.withFlux star5 = star.withFlux(1.0, bandpass) @@ -658,7 +663,6 @@ def test_chromatic_flux(): assert star7 == star9 - @timer def test_double_ChromaticSum(): ''' Test logic section of ChromaticConvolve that splits apart ChromaticSums for the case that diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index d79822f2416..5bec4d81a4a 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -773,24 +773,18 @@ def test_pixel(): gal2b = galsim.Pixel(scale = 1.7, flux = 100) gsobject_compare(gal2a, gal2b) - # The config stuff emits a warning about the rectangular pixel. - # We suppress that here, since we're doing it on purpose. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - gal3a = galsim.config.BuildGSObject(config, 'gal3')[0] - gal3b = galsim.Box(width = 2, height = 2.1, flux = 1.e6) - gal3b = gal3b.shear(q = 0.6, beta = 0.39 * galsim.radians) - # Drawing sheared Pixel without convolution doesn't work, so we need to - # do the extra convolution by a Gaussian here - gsobject_compare(gal3a, gal3b, conv=galsim.Gaussian(0.1)) - - gal4a = galsim.config.BuildGSObject(config, 'gal4')[0] - gal4b = galsim.Box(width = 1, height = 1.2, flux = 50) - gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees).magnify(1.03) - gal4b = gal4b.shear(g1 = 0.03, g2 = -0.05).shift(dx = 0.7, dy = -1.2) - gsobject_compare(gal4a, gal4b, conv=galsim.Gaussian(0.1)) + gal3a = galsim.config.BuildGSObject(config, 'gal3')[0] + gal3b = galsim.Box(width = 2, height = 2.1, flux = 1.e6) + gal3b = gal3b.shear(q = 0.6, beta = 0.39 * galsim.radians) + # Drawing sheared Pixel without convolution doesn't work, so we need to + # do the extra convolution by a Gaussian here + gsobject_compare(gal3a, gal3b, conv=galsim.Gaussian(0.1)) + + gal4a = galsim.config.BuildGSObject(config, 'gal4')[0] + gal4b = galsim.Box(width = 1, height = 1.2, flux = 50) + gal4b = gal4b.dilate(3).shear(e1 = 0.3).rotate(12 * galsim.degrees).magnify(1.03) + gal4b = gal4b.shear(g1 = 0.03, g2 = -0.05).shift(dx = 0.7, dy = -1.2) + gsobject_compare(gal4a, gal4b, conv=galsim.Gaussian(0.1)) @timer def test_realgalaxy(): @@ -1002,9 +996,7 @@ def test_cosmosgalaxy(): config['obj_num'] = 0 # It is going to complain that it doesn't have weight factors. We want to ignore this. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): gal1a = galsim.config.BuildGSObject(config, 'gal1')[0] gal1b = cosmos_cat.makeGalaxy(rng=rng) gsobject_compare(gal1a, gal1b, conv=conv) diff --git a/tests/test_convolve.py b/tests/test_convolve.py index d331ee5cffc..fac7ea71264 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -51,9 +51,7 @@ def test_convolve(): # Note: Since both of these have hard edges, GalSim wants to do this with real_space=True. # Here we are intentionally tesing the Fourier convolution, so we want to suppress the # warning that GalSim emits. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): # We'll do the real space convolution below conv = galsim.Convolve([psf,pixel],real_space=False) conv.drawImage(myImg,scale=dx, method="sb", use_true_center=False) @@ -92,8 +90,7 @@ def test_convolve(): check_basic(conv, "Moffat * Pixel") # Test photon shooting. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): do_shoot(conv,myImg,"Moffat * Pixel") # Clear the warnings registry for later so we can test that appropriate warnings are raised. galsim.Convolution.__init__.__globals__['__warningregistry__'].clear() @@ -134,6 +131,16 @@ def test_convolve(): assert_raises(TypeError, galsim.Convolution, [psf, psf, myImg]) assert_raises(TypeError, galsim.Convolution, [psf, psf], realspace=False) + with assert_warns(galsim.GalSimWarning): + triple = galsim.Convolve(psf, psf, pixel) + assert_raises(galsim.GalSimError, triple.xValue, galsim.PositionD(0,0)) + assert_raises(galsim.GalSimError, triple.drawReal, myImg) + + deconv = galsim.Convolve(psf, galsim.Deconvolve(pixel)) + assert_raises(galsim.GalSimError, deconv.xValue, galsim.PositionD(0,0)) + assert_raises(galsim.GalSimError, deconv.drawReal, myImg) + assert_raises(galsim.GalSimError, deconv.drawPhot, myImg, n_photons=10) + @timer def test_convolve_flux_scaling(): @@ -242,9 +249,7 @@ def test_shearconvolve(): check_basic(conv, "sheared Gaussian * Pixel") # Test photon shooting. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): do_shoot(conv,myImg,"sheared Gaussian * Pixel") # Clear the warnings registry for later so we can test that appropriate warnings are raised. galsim.GSObject.drawImage.__globals__['__warningregistry__'].clear() @@ -542,6 +547,10 @@ def test_deconvolve(): assert_raises(TypeError, galsim.Deconvolution, psf, psf) assert_raises(TypeError, galsim.Deconvolution, psf, real_space=False) + assert_raises(NotImplementedError, inv_obj.xValue, galsim.PositionD(0,0)) + assert_raises(NotImplementedError, inv_obj.drawReal, myImg1) + assert_raises(NotImplementedError, inv_obj.shoot, 1) + @timer def test_autoconvolve(): diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 200fadcda57..7265abcf519 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -28,17 +28,9 @@ def check_dep(f, *args, **kwargs): """Check that some function raises a GalSimDeprecationWarning as a warning, but not an error. """ - import warnings - # Cause all warnings to always be triggered. - # Important in case we want to trigger the same one twice in the test suite. - warnings.simplefilter("always") - # Check that f() raises a warning, but not an error. - with warnings.catch_warnings(record=True) as w: + with assert_warns(galsim.GalSimDeprecationWarning): res = f(*args, **kwargs) - assert len(w) >= 1, "Calling %s did not raise a warning"%str(f) - #print([ str(wk.message) for wk in w ]) - assert issubclass(w[0].category, galsim.GalSimDeprecationWarning) return res if __name__ == "__main__": diff --git a/tests/test_draw.py b/tests/test_draw.py index 242b2fb58d5..ecb3c8dff9e 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -291,13 +291,6 @@ def test_drawImage(): np.testing.assert_almost_equal( mom['My'], (ny+1.+1.)/2., 4, "obj.drawImage(nx,ny) (odd) did not center in y correctly") - # Test if we provide nx, ny, scale, and an existing image. It should: - # - raise a ValueError - im10 = galsim.ImageF() - kwargs = {'nx':nx, 'ny':ny, 'scale':scale, 'image':im10} - with assert_raises(ValueError): - obj.drawImage(**kwargs) - # Test if we provide bounds and scale. It should: # - create a new image with the right size # - set the scale @@ -332,12 +325,39 @@ def test_drawImage(): np.testing.assert_almost_equal(mom['My'], (ny+1.+1.)/2., 4, "obj.drawImage(bounds) did not center in y correctly") - # Test if we provide bounds, scale, and an existing image. It should: - # - raise a ValueError - bounds = galsim.BoundsI(1,nx,1,ny) - kwargs = {'bounds':bounds, 'scale':scale, 'image':im10} - with assert_raises(ValueError): - obj.drawImage(**kwargs) + # Combinations that raise errors: + assert_raises(TypeError, obj.drawImage, image=im10, bounds=bounds) + assert_raises(TypeError, obj.drawImage, image=im10, dtype=int) + assert_raises(TypeError, obj.drawImage, nx=3, ny=4, image=im10, scale=scale) + assert_raises(TypeError, obj.drawImage, nx=3, ny=4, image=im10) + assert_raises(TypeError, obj.drawImage, nx=3, ny=4, bounds=bounds) + assert_raises(TypeError, obj.drawImage, nx=3, ny=4, add_to_image=True) + assert_raises(TypeError, obj.drawImage, bounds=bounds, add_to_image=True) + assert_raises(TypeError, obj.drawImage, image=galsim.Image(), add_to_image=True) + assert_raises(TypeError, obj.drawImage, nx=3) + assert_raises(TypeError, obj.drawImage, ny=3) + assert_raises(TypeError, obj.drawImage, nx=3, ny=3, invalid=True) + assert_raises(TypeError, obj.drawImage, bounds=bounds, scale=scale, wcs=galsim.PixelScale(3)) + assert_raises(TypeError, obj.drawImage, bounds=bounds, wcs=scale) + assert_raises(TypeError, obj.drawImage, image=im10.array) + assert_raises(TypeError, obj.drawImage, wcs=galsim.FitsWCS('fits_files/tpv.fits')) + + assert_raises(ValueError, obj.drawImage, bounds=galsim.BoundsI()) + assert_raises(ValueError, obj.drawImage, image=im10, gain=0.) + assert_raises(ValueError, obj.drawImage, image=im10, gain=-1.) + assert_raises(ValueError, obj.drawImage, image=im10, area=0.) + assert_raises(ValueError, obj.drawImage, image=im10, area=-1.) + assert_raises(ValueError, obj.drawImage, image=im10, exptime=0.) + assert_raises(ValueError, obj.drawImage, image=im10, exptime=-1.) + assert_raises(ValueError, obj.drawImage, image=im10, method='invalid') + + # These options are invalid unless metho=phot + assert_raises(TypeError, obj.drawImage, image=im10, n_photons=3) + assert_raises(TypeError, obj.drawImage, rng=galsim.BaseDeviate(234)) + assert_raises(TypeError, obj.drawImage, max_extra_noise=23) + assert_raises(TypeError, obj.drawImage, poisson_flux=True) + assert_raises(TypeError, obj.drawImage, surface_ops=('dummy')) + assert_raises(TypeError, obj.drawImage, save_photons=True) @timer @@ -433,9 +453,7 @@ def test_draw_methods(): "obj.drawImage(real_space) differs from obj.drawImage") # fft should be similar, but not precisely equal. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): # This emits a warning about convolving two things with hard edges. im3 = obj.drawImage(image=im1.copy(), method='fft') print('im1, im3 max diff = ',abs(im1.array - im3.array).max()) @@ -714,12 +732,52 @@ def test_drawKImage(): np.testing.assert_almost_equal( im6.scale, scale, 9, "obj.drawKImage(image,recenter=False) produced image with wrong scale") - np.testing.assert_equal( - im6.array.shape, (ny//4+1, nx//3+1), - "obj.drawKImage(image,recenter=False) produced image with wrong shape") np.testing.assert_almost_equal( im6.array, im4[bounds6].array, 9, - "obj.drawKImage(image,rcenter=False) produced different values than recenter=True") + "obj.drawKImage(image,recenter=False) produced different values than recenter=True") + + # Can add to image if recenter is False + im6.setZero() + obj.drawKImage(im6, recenter=False, add_to_image=True) + np.testing.assert_almost_equal( + im6.scale, scale, 9, + "obj.drawKImage(image,add_to_image=True) produced image with wrong scale") + np.testing.assert_almost_equal( + im6.array, im4[bounds6].array, 9, + "obj.drawKImage(image,add_to_image=True) produced different values than recenter=True") + + # .. or if image is centered. + im7 = im4.copy() + im7.setZero() + im7.setCenter(0,0) + obj.drawKImage(im7, add_to_image=True) + np.testing.assert_almost_equal( + im7.scale, scale, 9, + "obj.drawKImage(image,add_to_image=True) produced image with wrong scale") + np.testing.assert_almost_equal( + im7.array, im4.array, 9, + "obj.drawKImage(image,add_to_image=True) produced different values than recenter=True") + + # .. but otherwise not. + with assert_raises(galsim.GalSimIncompatibleValuesError): + obj.drawKImage(image=im6, add_to_image=True) + + # Other error combinations: + assert_raises(TypeError, obj.drawKImage, image=im6, bounds=bounds) + assert_raises(TypeError, obj.drawKImage, image=im6, dtype=int) + assert_raises(TypeError, obj.drawKImage, nx=3, ny=4, image=im6, scale=scale) + assert_raises(TypeError, obj.drawKImage, nx=3, ny=4, image=im6) + assert_raises(TypeError, obj.drawKImage, nx=3, ny=4, add_to_image=True) + assert_raises(TypeError, obj.drawKImage, nx=3, ny=4, bounds=bounds) + assert_raises(TypeError, obj.drawKImage, bounds=bounds, add_to_image=True) + assert_raises(TypeError, obj.drawKImage, image=galsim.Image(dtype=complex), add_to_image=True) + assert_raises(TypeError, obj.drawKImage, nx=3) + assert_raises(TypeError, obj.drawKImage, ny=3) + assert_raises(TypeError, obj.drawKImage, nx=3, ny=3, invalid=True) + assert_raises(TypeError, obj.drawKImage, bounds=bounds, wcs=galsim.PixelScale(3)) + assert_raises(TypeError, obj.drawKImage, image=im6.array) + assert_raises(ValueError, obj.drawKImage, image=galsim.ImageF(3,4)) + assert_raises(ValueError, obj.drawKImage, bounds=galsim.BoundsI()) @timer @@ -1005,6 +1063,11 @@ def test_shoot(): image4 = (obj*0).drawImage(method='phot') np.testing.assert_equal(image4.array, 0) + # Warns if flux is 1 and n_photons not given. + with assert_warns(galsim.GalSimWarning): + psf = galsim.Gaussian(sigma=3) + psf.drawImage(method='phot') + @timer def test_drawImage_area_exptime(): @@ -1050,10 +1113,8 @@ def test_drawImage_area_exptime(): with assert_warns(galsim.GalSimWarning): obj1.drawImage(method='phot') # But not if we explicitly tell it to shoot 1 photon - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("error") - obj1.drawImage(method='phot', n_photons=1) + with assert_raises(AssertionError): + assert_warns(galsim.GalSimWarning, obj1.drawImage, method='phot', n_photons=1) @timer diff --git a/tests/test_exponential.py b/tests/test_exponential.py index 7945c10696d..9032d47cd42 100644 --- a/tests/test_exponential.py +++ b/tests/test_exponential.py @@ -78,6 +78,8 @@ def test_exponential(): # Should raise an exception if both scale_radius and half_light_radius are provided. assert_raises(TypeError, galsim.Exponential, scale_radius=3, half_light_radius=1) + # Or neither. + assert_raises(TypeError, galsim.Exponential) @timer diff --git a/tests/test_fouriersqrt.py b/tests/test_fouriersqrt.py index 9c40c08a4a2..fac7cd6b5cb 100644 --- a/tests/test_fouriersqrt.py +++ b/tests/test_fouriersqrt.py @@ -98,5 +98,9 @@ def test_fourier_sqrt(): assert_raises(TypeError, galsim.FourierSqrtProfile, psf, psf) assert_raises(TypeError, galsim.FourierSqrtProfile, psf, real_space=False) + assert_raises(NotImplementedError, sqrt1.xValue, galsim.PositionD(0,0)) + assert_raises(NotImplementedError, sqrt1.drawReal, myImg1) + assert_raises(NotImplementedError, sqrt1.shoot, 1) + if __name__ == "__main__": test_fourier_sqrt() diff --git a/tests/test_gaussian.py b/tests/test_gaussian.py index 9e1d0d48799..e51b2a6c7b1 100644 --- a/tests/test_gaussian.py +++ b/tests/test_gaussian.py @@ -105,12 +105,17 @@ def test_gaussian(): assert_raises(TypeError, galsim.Gaussian, half_light_radius=1, fwhm=2) assert_raises(TypeError, galsim.Gaussian, sigma=3, fwhm=2) assert_raises(TypeError, galsim.Gaussian, sigma=3, half_light_radius=1) + # Or none. + assert_raises(TypeError, galsim.Gaussian) # Finally, test the noise property for things that don't have any noise set. assert gauss.noise is None # And accessing the attribute from the class should indicate that it is a lazyproperty assert 'lazy_property' in str(galsim.GSObject._noise) + # And check that trying to use GSObject directly is an error. + assert_raises(NotImplementedError, galsim.GSObject) + @timer def test_gaussian_properties(): @@ -136,6 +141,22 @@ def test_gaussian_properties(): outFlux = gauss.flux np.testing.assert_almost_equal(outFlux, inFlux) + # Check some valid and invalid ways to pass arguments to xValue + # Same code applies to kValue and others, so just do this one. + assert gauss.xValue(cen.x, cen.y) == gauss.xValue(cen) + assert gauss.xValue(x=cen.x, y=cen.y) == gauss.xValue(cen) + assert gauss.xValue( (cen.x, cen.y) ) == gauss.xValue(cen) + assert_raises(TypeError, gauss.xValue, cen.x) + assert_raises(TypeError, gauss.xValue, x=cen.x) + assert_raises(TypeError, gauss.xValue, cen.x, y=cen.y) + assert_raises(TypeError, gauss.xValue, dx=cen.x, dy=cen.y) + assert_raises(TypeError, gauss.xValue, dx=cen.x, y=cen.y) + assert_raises(TypeError, gauss.xValue, x=cen.x, dy=cen.y) + assert_raises(TypeError, gauss.xValue, cen.x, cen.y, cen.y) + assert_raises(TypeError, gauss.xValue, cen.x, cen.y, invalid=True) + assert_raises(TypeError, gauss.xValue, pos=cen) + + @timer def test_gaussian_radii(): diff --git a/tests/test_inclined.py b/tests/test_inclined.py index 18e9ffd0149..15e699890cb 100644 --- a/tests/test_inclined.py +++ b/tests/test_inclined.py @@ -644,6 +644,12 @@ def test_exceptions(): get_prof("InclinedSersic", inclination = 0.*galsim.degrees, scale_radius = 1., trunc = -4.5) + # trunc can't be too small in InclinedSersic + with assert_raises(ValueError): + get_prof("InclinedSersic", inclination = 0.*galsim.degrees, + half_light_radius = 1., trunc = 1.4) + + @timer def test_value_retrieval(): """ Tests to make sure that if a parameter is passed to a profile, we get back the same diff --git a/tests/test_kolmogorov.py b/tests/test_kolmogorov.py index d3d1d797815..46a74d361e7 100644 --- a/tests/test_kolmogorov.py +++ b/tests/test_kolmogorov.py @@ -117,6 +117,7 @@ def test_kolmogorov(): assert_raises(TypeError, galsim.Kolmogorov, half_light_radius=1, r0=1) assert_raises(TypeError, galsim.Kolmogorov, lam=3) assert_raises(TypeError, galsim.Kolmogorov, r0=1) + assert_raises(TypeError, galsim.Kolmogorov) @timer def test_kolmogorov_properties(): diff --git a/tests/test_moffat.py b/tests/test_moffat.py index 68991b2f68f..981fc7ed219 100644 --- a/tests/test_moffat.py +++ b/tests/test_moffat.py @@ -99,7 +99,18 @@ def test_moffat(): assert_raises(TypeError, galsim.Moffat, beta=3, half_light_radius=1, fwhm=2) assert_raises(TypeError, galsim.Moffat, beta=3, scale_radius=3, fwhm=2) assert_raises(TypeError, galsim.Moffat, beta=3, scale_radius=3, half_light_radius=1) + assert_raises(TypeError, galsim.Moffat, beta=3) + # beta <= 1.1 needs to be truncated. + assert_raises(ValueError, galsim.Moffat, beta=1.1, scale_radius=3) + assert_raises(ValueError, galsim.Moffat, beta=0.9, scale_radius=3) + + # trunc must be > sqrt(2) * hlr + assert_raises(ValueError, galsim.Moffat, beta=3, half_light_radius=1, trunc=1.4) + + # Other errors + assert_raises(TypeError, galsim.Moffat, scale_radius=3) + assert_raises(ValueError, galsim.Moffat, beta=3, scale_radius=3, trunc=-1) @timer def test_moffat_properties(): diff --git a/tests/test_photon_array.py b/tests/test_photon_array.py index 3e4478b06c5..d94f7fa0cd5 100644 --- a/tests/test_photon_array.py +++ b/tests/test_photon_array.py @@ -329,6 +329,9 @@ def test_photon_io(): photons = image.photons assert photons.size() == len(photons) == nphotons + with assert_raises(galsim.GalSimIncompatibleValuesError): + obj.drawImage(method='phot', n_photons=nphotons, save_photons=True, maxN=1.e5) + file_name = 'output/photons1.dat' photons.write(file_name) diff --git a/tests/test_sersic.py b/tests/test_sersic.py index 103716917e3..8810331e517 100644 --- a/tests/test_sersic.py +++ b/tests/test_sersic.py @@ -137,7 +137,22 @@ def test_sersic(): # Should raise an exception if both scale_radius and half_light_radius are provided. assert_raises(TypeError, galsim.Sersic, n=1.2, scale_radius=3, half_light_radius=1) + assert_raises(TypeError, galsim.Sersic, n=1.2) assert_raises(TypeError, galsim.DeVaucouleurs, scale_radius=3, half_light_radius=1) + assert_raises(TypeError, galsim.DeVaucouleurs) + + # Allowed range is [0.3, 6.2] + assert_raises(ValueError, galsim.Sersic, n=0.2, scale_radius=3) + assert_raises(ValueError, galsim.Sersic, n=6.3, scale_radius=3) + + # trunc must be > sqrt(2) * hlr + assert_raises(ValueError, galsim.Sersic, n=3, half_light_radius=1, trunc=1.4) + assert_raises(ValueError, galsim.DeVaucouleurs, half_light_radius=1, trunc=1.4) + + # Other errors + assert_raises(TypeError, galsim.Sersic, scale_radius=3) + assert_raises(ValueError, galsim.Sersic, n=3, scale_radius=3, trunc=-1) + assert_raises(ValueError, galsim.DeVaucouleurs, scale_radius=3, trunc=-1) @timer diff --git a/tests/test_shapelet.py b/tests/test_shapelet.py index 4eda4371cce..b2b192a8710 100644 --- a/tests/test_shapelet.py +++ b/tests/test_shapelet.py @@ -171,6 +171,11 @@ def test_shapelet_properties(): # Check picklability do_pickle(shapelet) + assert_raises(TypeError, galsim.Shapelet, sigma=sigma) + assert_raises(TypeError, galsim.Shapelet, sigma=sigma, bvec=bvec) + assert_raises(TypeError, galsim.Shapelet, order=order, bvec=bvec) + assert_raises(ValueError, galsim.Shapelet, sigma=sigma, order=5, bvec=bvec) + @timer def test_shapelet_fit(): @@ -236,6 +241,13 @@ def test_shapelet_fit(): np.testing.assert_almost_equal(shapelet.bvec, shapelet2.bvec, 6, err_msg="Second fitted shapelet coefficients do not match original") + assert_raises(ValueError, galsim.Shapelet.fit, sigma, 10, im1, normalization='invalid') + + # Haven't gotten around to implementing this yet... + im2.wcs = galsim.JacobianWCS(0.2,0.01,0.01,0.2) + with assert_raises(NotImplementedError): + galsim.Shapelet.fit(sigma, 10, im2) + @timer def test_shapelet_adjustments(): diff --git a/tests/test_spergel.py b/tests/test_spergel.py index 647146ed947..f3162a4d55f 100644 --- a/tests/test_spergel.py +++ b/tests/test_spergel.py @@ -90,6 +90,12 @@ def test_spergel(): # Should raise an exception if both scale_radius and half_light_radius are provided. assert_raises(TypeError, galsim.Spergel, nu=0, scale_radius=3, half_light_radius=1) + assert_raises(TypeError, galsim.Spergel, nu=0) + assert_raises(TypeError, galsim.Spergel, scale_radius=3) + + # Allowed range = [-0.85, 4.0] + assert_raises(ValueError, galsim.Spergel, nu=-0.9) + assert_raises(ValueError, galsim.Spergel, nu=4.1) @timer diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 55548366dac..2227427e399 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -92,6 +92,12 @@ def test_smallshear(): # Check really small shear (This mostly tests a branch in the str function.) do_pickle(galsim.Gaussian(sigma=2.3).shear(g1=1.e-13,g2=0)) + assert_raises(TypeError, gauss.shear) + assert_raises(TypeError, gauss.shear, 0.3) + assert_raises(TypeError, gauss.shear, 0.1, 0.3) + assert_raises(TypeError, gauss.shear, g1=0.1, g2=0.1, invalid=0.3) + assert_raises(TypeError, gauss.shear, myShear, invalid=0.3) + @timer def test_largeshear(): """Test the application of a large shear to a Sersic profile against a known result. @@ -197,6 +203,8 @@ def test_rotate(): do_pickle(gal, lambda x: x.drawImage()) do_pickle(gal) + assert_raises(TypeError, gal.rotate) + assert_raises(TypeError, gal.rotate, 34) @timer def test_mag(): @@ -867,6 +875,7 @@ def test_ne(): objs = [galsim.Transform(gal1), galsim.Transform(gal2), galsim.Transform(gal1, jac=(1, 0.5, 0.5, 1)), + galsim.Transform(gal1, jac=(1, 1, 1, 1)), galsim.Transform(gal1, jac=jac), galsim.Transform(gal1, offset=galsim.PositionD(2, 2)), galsim.Transform(gal1, offset=offset), @@ -875,6 +884,12 @@ def test_ne(): galsim.Transform(gal1, gsparams=gsp)] all_obj_diff(objs) + # The degenerate jacobian will build fine, but will raise an exception when used. + degen = galsim.Transform(gal1, jac=(1, 1, 1, 1)) + with assert_raises(galsim.GalSimError): + sbp = degen._sbp + + @timer def test_compound(): """Check that transformations of transformations work the same whether they are compounded From 088d72e2324b2bfb56b291c0a29ff8b192b25c12 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 28 Apr 2018 19:49:05 -0400 Subject: [PATCH 50/96] Increase test coverage of interpolatedimage (#755) --- galsim/interpolatedimage.py | 66 ++++++++++++-------- galsim/real.py | 7 ++- tests/test_config_gsobject.py | 9 +-- tests/test_interpolatedimage.py | 105 +++++++++++++++++++++++--------- 4 files changed, 126 insertions(+), 61 deletions(-) diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 14694c591a3..47022a194f9 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -273,14 +273,14 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= use_cache=True, use_true_center=True, offset=None, gsparams=None, _force_stepk=0., _force_maxk=0., hdu=None): - from .wcs import BaseWCS, PixelScale - from .wcs import BaseWCS, PixelScale from .random import BaseDeviate # If the "image" is not actually an image, try to read the image as a file. - if not isinstance(image, Image): + if isinstance(image, str): image = fits.read(image, hdu=hdu) + elif not isinstance(image, Image): + raise TypeError("Supplied image must be an Image or file name") # make sure image is really an image and has a float type if image.dtype != np.float32 and image.dtype != np.float64: @@ -344,8 +344,8 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= # I think the only things that will mess up if flux == 0 are the # calculateStepK and calculateMaxK functions, and rescaling the flux to some value. if (calculate_stepk or calculate_maxk or flux is not None) and self._image_flux == 0.: - raise GalSimError("This input image has zero total flux. " - "It does not define a valid surface brightness profile.") + raise GalSimValueError("This input image has zero total flux. It does not define a " + "valid surface brightness profile.", image) # Process the different options for flux, stepk, maxk self._flux = self._getFlux(flux, normalization) @@ -394,13 +394,13 @@ def image(self): def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, use_cache): # Check that given pad_image is valid: - if pad_image: + if pad_image is not None: if isinstance(pad_image, basestring): pad_image = fits.read(pad_image) - else: + elif isinstance(pad_image, Image): pad_image = pad_image._view() - if not isinstance(pad_image, Image): - raise GalSimValueError("Supplied pad_image must be an Image.", pad_image) + else: + raise TypeError("Supplied pad_image must be an Image.", pad_image) if pad_image.dtype != np.float32 and pad_image.dtype != np.float64: raise GalSimValueError("Invalid dtype for Supplied pad_image.", pad_image.dtype, (np.float32, np.float64)) @@ -412,8 +412,19 @@ def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, # Use the minimum scale, since we want to make sure noise_pad_size is # as large as we need in any direction. if noise_pad_size: + if noise_pad_size < 0: + raise GalSimValuesError("noise_pad_size may not be negative", noise_pad_size) + if not noise_pad: + raise GalSimIncompatibleValuesError( + "Must provide noise_pad if noise_pad_size > 0", + noise_pad=noise_pad, noise_pad_size=noise_pad_size) noise_pad_size = int(math.ceil(noise_pad_size / self._wcs._minScale())) noise_pad_size = Image.good_fft_size(noise_pad_size) + else: + if noise_pad: + raise GalSimIncompatibleValuesError( + "Must provide noise_pad_size if noise_pad != 0", + noise_pad=noise_pad, noise_pad_size=noise_pad_size) # The size of the final padded image is the largest of the various size specifications pad_size = max(self._image.array.shape) @@ -432,7 +443,7 @@ def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, # If requested, fill (some of) this image with noise padding. nz_bounds = self._image.bounds - if noise_pad_size > 0 and noise_pad is not None: + if noise_pad: # This is a bit involved, so pass this off to another helper function. b = self._buildNoisePadImage(noise_pad_size, noise_pad, rng, use_cache) nz_bounds += b @@ -471,7 +482,6 @@ def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): # Figure out what kind of noise to apply to the image try: noise_pad = float(noise_pad) - except (TypeError, ValueError): if isinstance(noise_pad, _BaseCorrelatedNoise): noise = noise_pad.copy(rng=rng1) @@ -660,15 +670,13 @@ def _xValue(self, pos): def _kValue(self, kpos): return self._sbp.kValue(kpos._p) - @doc_inherit - def shoot(self, n_photons, rng=None): - if isinstance(self.x_interpolant, SincInterpolant): - raise GalSimError("Photon shooting is not practical with SincInterpolant") - return GSObject.shoot(self, n_photons, rng) - @doc_inherit def _shoot(self, photons, ud): - self._sbp.shoot(photons._pa, ud._rng) + try: + self._sbp.shoot(photons._pa, ud._rng) + except RuntimeError: + xi = self.x_interpolant.__class__.__name__ + raise GalSimError("Photon shooting is not practical with x_interpolant of type %s"%xi) @doc_inherit def _drawReal(self, image): @@ -841,17 +849,16 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimage) # If the "image" is not actually an image, try to read the image as a file. - if not isinstance(real_kimage, Image): + if isinstance(real_kimage, str): real_kimage = fits.read(real_kimage, hdu=real_hdu) - if not isinstance(imag_kimage, Image): + elif not isinstance(real_kimage, Image): + raise TypeError("real_kimage must be either an Image or a file name") + if isinstance(imag_kimage, str): imag_kimage = fits.read(imag_kimage, hdu=imag_hdu) + elif not isinstance(imag_kimage, Image): + raise TypeError("imag_kimage must be either an Image or a file name") - # make sure real_kimage, imag_kimage are really `Image`s, are floats, and are - # congruent. - if not isinstance(real_kimage, Image): - raise GalSimValueError("Supplied real_kimage is not an Image instance", real_kimage) - if not isinstance(imag_kimage, Image): - raise GalSimValueError("Supplied imag_kimage is not an Image instance", imag_kimage) + # make sure real_kimage, imag_kimage are congruent. if real_kimage.bounds != imag_kimage.bounds: raise GalSimIncompatibleValuesError( "Real and Imag kimages must have same bounds.", @@ -866,7 +873,9 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, if real_kimage is not None or imag_kimage is not None: raise GalSimIncompatibleValuesError( "Cannot provide both kimage and real_kimage/imag_kimage", - kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimag) + kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimage) + if not isinstance(kimage, Image): + raise TypeError("kimage must be a galsim.Image isntance") if not kimage.iscomplex: raise GalSimValueError("Supplied kimage is not complex", kimage) @@ -874,6 +883,9 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, if kimage.wcs is not None and not kimage.wcs.isPixelScale(): raise GalSimValueError("kimage wcs must be PixelScale or None.", kimage.wcs) + if not kimage.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Supplied image does not have bounds defined.") + self._kimage = kimage.copy() self._gsparams = GSParams.check(gsparams) diff --git a/galsim/real.py b/galsim/real.py index 9e9c0a17cde..b845ce6b14f 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -1225,8 +1225,13 @@ def _initialize(self, imgs, bands, xis, PSFs, # Get Fourier-space representations of input imgs. kimgs = np.empty((Nim, nk, nk), dtype=np.complex128) + if noise_pad_size == 0: + noise_pad = 0. + for i, (img, xi) in enumerate(zip(imgs, xis)): - ii = InterpolatedImage(img, noise_pad_size=noise_pad_size, noise_pad=xi, + if noise_pad_size != 0: + noise_pad = xi + ii = InterpolatedImage(img, noise_pad_size=noise_pad_size, noise_pad=noise_pad, rng=self.rng, pad_factor=pad_factor) kimgs[i] = ii.drawKImage(nx=nk, ny=nk, scale=stepk).array diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index 5bec4d81a4a..9ab72d32bff 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -1020,10 +1020,10 @@ def test_interpolated_image(): 'x_interpolant' : 'lanczos5', 'scale' : 0.7, 'flux' : 1.e5 }, 'gal5' : { 'type' : 'InterpolatedImage', 'image' : file_name, - 'noise_pad' : 0.001 + 'noise_pad' : 0.001, 'noise_pad_size' : 64, }, 'gal6' : { 'type' : 'InterpolatedImage', 'image' : file_name, - 'noise_pad' : 'fits_files/blankimg.fits' + 'noise_pad' : 'fits_files/blankimg.fits', 'noise_pad_size' : 64, }, 'gal7' : { 'type' : 'InterpolatedImage', 'image' : file_name, 'pad_image' : 'fits_files/blankimg.fits' @@ -1054,11 +1054,12 @@ def test_interpolated_image(): gsobject_compare(gal4a, gal4b) gal5a = galsim.config.BuildGSObject(config, 'gal5')[0] - gal5b = galsim.InterpolatedImage(im, rng=rng, noise_pad=0.001) + gal5b = galsim.InterpolatedImage(im, rng=rng, noise_pad=0.001, noise_pad_size=64) gsobject_compare(gal5a, gal5b) gal6a = galsim.config.BuildGSObject(config, 'gal6')[0] - gal6b = galsim.InterpolatedImage(im, rng=rng, noise_pad='fits_files/blankimg.fits') + gal6b = galsim.InterpolatedImage(im, rng=rng, noise_pad='fits_files/blankimg.fits', + noise_pad_size=64) gsobject_compare(gal6a, gal6b) gal7a = galsim.config.BuildGSObject(config, 'gal7')[0] diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index c032289ba0f..62720017e0b 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -229,36 +229,48 @@ def test_fluxnorm(): def test_exceptions(): """Test failure modes for InterpolatedImage class. """ - # What if it receives as input something that is not an Image? Give it a GSObject to check. - g = galsim.Gaussian(sigma=1.) - with assert_raises((TypeError, AttributeError)): - galsim.InterpolatedImage(g) - # What if Image does not have a scale set, but scale keyword is not specified? - im = galsim.ImageF(5, 5) - with assert_raises(ValueError): - galsim.InterpolatedImage(im) - # Image must have bounds defined - im = galsim.ImageF() - im.scale = 1. + # Check that provided image has valid bounds with assert_raises(galsim.GalSimUndefinedBoundsError): - galsim.InterpolatedImage(im) - # Weird flux normalization - im = galsim.ImageF(5, 5, scale=1.) - with assert_raises(ValueError): - galsim.InterpolatedImage(im, normalization = 'foo') - # scale and WCS - with assert_raises(TypeError): - galsim.InterpolatedImage(im, wcs = galsim.PixelScale(1.), scale=1.) - # weird WCS - with assert_raises(TypeError): - galsim.InterpolatedImage(im, wcs = 1.) - # Weird interpolant - give it something random like a GSObject - with assert_raises(Exception): - galsim.InterpolatedImage(im, x_interpolant = g) - # Image has wrong type - im = galsim.ImageI(5, 5) - with assert_raises(ValueError): - galsim.InterpolatedImage(im) + galsim.InterpolatedImage(image=galsim.ImageF(scale=1.)) + + # Scale must be set + with assert_raises(galsim.GalSimIncompatibleValuesError): + galsim.InterpolatedImage(image=galsim.ImageF(5, 5)) + + # Image must be real type (F or D) + with assert_raises(galsim.GalSimValueError): + galsim.InterpolatedImage(image=galsim.ImageI(5, 5, scale=1)) + + # Image must have non-zero flux + with assert_raises(galsim.GalSimValueError): + galsim.InterpolatedImage(image=galsim.ImageF(5, 5, scale=1, init_value=0.)) + + # Can't shoot II with SincInterpolant + ii = galsim.InterpolatedImage(image=galsim.ImageF(5, 5, scale=1, init_value=1.), + x_interpolant='sinc') + with assert_raises(galsim.GalSimError): + ii.drawImage(method='phot') + with assert_raises(galsim.GalSimError): + ii.shoot(n_photons=3) + + # Check types of inputs + im = galsim.ImageF(5, 5, scale=1., init_value=10.) + assert_raises(TypeError, galsim.InterpolatedImage, image=im.array) + assert_raises(TypeError, galsim.InterpolatedImage, im, wcs=galsim.PixelScale(1.), scale=1.) + assert_raises(TypeError, galsim.InterpolatedImage, im, wcs=1.) + assert_raises(TypeError, galsim.InterpolatedImage, im, pad_image=im.array) + assert_raises(TypeError, galsim.InterpolatedImage, im, noise_pad_size=33) + assert_raises(TypeError, galsim.InterpolatedImage, im, noise_pad=33) + + # Other invalid values: + assert_raises(ValueError, galsim.InterpolatedImage, im, normalization='invalid') + assert_raises(ValueError, galsim.InterpolatedImage, im, x_interpolant='invalid') + assert_raises(ValueError, galsim.InterpolatedImage, im, k_interpolant='invalid') + assert_raises(ValueError, galsim.InterpolatedImage, im, pad_image=galsim.ImageI(25,25)) + assert_raises(ValueError, galsim.InterpolatedImage, im, pad_factor=0.) + assert_raises(ValueError, galsim.InterpolatedImage, im, pad_factor=-1.) + assert_raises(ValueError, galsim.InterpolatedImage, im, noise_pad_size=33, noise_pad=im.wcs) + assert_raises(ValueError, galsim.InterpolatedImage, im, noise_pad_size=33, noise_pad=-1.) @timer @@ -1188,6 +1200,41 @@ def test_kroundtrip(): np.testing.assert_array_almost_equal(a_conv_c_img.array, b_conv_c_img.array, 5, "Convolution of InterpolatedKImage drawn incorrectly.") + +@timer +def test_kexceptions(): + """Test failure modes for InterpolatedKImage class. + """ + # Check that provided image has valid bounds + with assert_raises(galsim.GalSimUndefinedBoundsError): + galsim.InterpolatedKImage(kimage=galsim.ImageCD(scale=1.)) + + # Image must be complex type (CF or CD) + with assert_raises(galsim.GalSimValueError): + galsim.InterpolatedKImage(kimage=galsim.ImageD(5, 5, scale=1)) + + # Check types of inputs + im = galsim.ImageCD(5, 5, scale=1., init_value=10.) + assert_raises(TypeError, galsim.InterpolatedKImage) + assert_raises(TypeError, galsim.InterpolatedKImage, kimage=im.array) + assert_raises(TypeError, galsim.InterpolatedKImage, real_kimage=im.real, imag_kimage=4) + assert_raises(TypeError, galsim.InterpolatedKImage, real_kimage=3, imag_kimage=im.imag) + assert_raises(TypeError, galsim.InterpolatedKImage, kimage=im, + real_kimage=im.real, imag_kimage=im.imag) + + # Other invalid values: + assert_raises(ValueError, galsim.InterpolatedKImage, im, k_interpolant='invalid') + assert_raises(ValueError, galsim.InterpolatedKImage, real_kimage=im.real) + assert_raises(ValueError, galsim.InterpolatedKImage, imag_kimage=im.imag) + assert_raises(ValueError, galsim.InterpolatedKImage, real_kimage=im, imag_kimage=im) + assert_raises(ValueError, galsim.InterpolatedKImage, real_kimage=im.real, + imag_kimage=galsim.ImageD(4,4,scale=1.)) + assert_raises(ValueError, galsim.InterpolatedKImage, real_kimage=im.real, + imag_kimage=galsim.ImageD(5,5,scale=2.)) + assert_raises(ValueError, galsim.InterpolatedKImage, + kimage=galsim.ImageCD(5, 5, wcs=galsim.JacobianWCS(2.1, 0.3, -0.4, 2.3))) + + @timer def test_multihdu_readin(): """Test the ability to read in from a file with multiple FITS extensions. From 6d5533d98f911786af9eb92a21777d0a0629ef29 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 28 Apr 2018 21:06:19 -0400 Subject: [PATCH 51/96] Increase test coverage of realgalaxy (#755) --- galsim/real.py | 15 +++++----- tests/test_real.py | 75 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/galsim/real.py b/galsim/real.py index b845ce6b14f..679921236ad 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -36,6 +36,7 @@ import numpy as np from .gsobject import GSObject +from .gsparams import GSParams from .chromatic import ChromaticSum from .position import PositionD from .utilities import doc_inherit @@ -290,7 +291,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, self._input_flux = flux self._flux_rescale = flux_rescale self._area_norm = area_norm - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) # Convert noise_pad to the right noise to pass to InterpolatedImage if noise_pad_size: @@ -542,7 +543,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, sample=sample, file_name=file_name) from ._pyfits import pyfits - self.file_name, self.image_dir, _ = _parse_files_dirs(file_name, dir, sample) + self.file_name, self.image_dir, self.sample = _parse_files_dirs(file_name, dir, sample) with pyfits.open(self.file_name) as fits: self.cat = fits[1].data @@ -858,9 +859,9 @@ def _parse_files_dirs(file_name, image_dir, sample): 'COSMOS_'+use_sample+'_training_sample') full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): - raise GalSimError('No RealGalaxy catalog found in %s. Run the program ' - 'galsim_download_cosmos -s %s to download catalog and accompanying ' - 'image files.'%(image_dir, use_sample)) + raise OSError('No RealGalaxy catalog found in %s. Run the program ' + 'galsim_download_cosmos -s %s to download catalog and accompanying ' + 'image files.'%(image_dir, use_sample)) elif image_dir is None: full_file_name = file_name image_dir = os.path.dirname(file_name) @@ -869,7 +870,7 @@ def _parse_files_dirs(file_name, image_dir, sample): if not os.path.isfile(full_file_name): raise OSError(full_file_name+' not found.') if not os.path.isdir(image_dir): - raise OSError(image_dir+' directory does not exist!') + raise OSError(image_dir+' directory does not exist.') return full_file_name, image_dir, use_sample @@ -1163,7 +1164,7 @@ def _initialize(self, imgs, bands, xis, PSFs, self._area_norm = area_norm self._k_interpolant = k_interpolant - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) NSED = len(self.SEDs) Nim = len(imgs) diff --git a/tests/test_real.py b/tests/test_real.py index 9a5b8debbc5..eb5486b8d04 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -49,6 +49,66 @@ def moments_to_ellip(mxx, myy, mxy): sig = (mxx*myy - mxy**2)**(0.25) return e1, e2, sig +@timer +def test_real_galaxy_catalog(): + """Test basic operations of RealGalaxyCatalog""" + + # Start with the test RGC that we will use throughout this test file. + rgc = galsim.RealGalaxyCatalog(file_name=catalog_file, dir=image_dir) + + print('sample = ',rgc.sample) + print('ident = ',rgc.ident) + assert(rgc.sample == None) + assert(len(rgc.ident) == 2) + + gal1 = rgc.getGalImage(0) + assert isinstance(gal1, galsim.Image) + psf1 = rgc.getGalImage(0) + assert isinstance(psf1, galsim.Image) + noise, scale, var = rgc.getNoiseProperties(0) + assert noise is None # No noise images for the test catalog. + print('noise info = ',noise, scale, var) + np.testing.assert_almost_equal(scale, 0.03) + assert var < 1.e-5 + + assert rgc.getIndexForID(100533) == 0 + + assert_raises(TypeError, galsim.RealGalaxyCatalog, catalog_file, dir=image_dir, sample='25.2') + assert_raises(ValueError, galsim.RealGalaxyCatalog, sample='23.2') + assert_raises(ValueError, galsim.RealGalaxyCatalog, sample='23.2') + assert_raises(OSError, galsim.RealGalaxyCatalog, file_name='invalid.fits') + assert_raises(ValueError, rgc.getIndexForID, 1234) + assert_raises(IndexError, rgc.getGalImage, 5) + assert_raises(IndexError, rgc.getPSFImage, 5) + assert_raises(IndexError, rgc.getNoiseProperties, 5) + + # The test catalog doesn't have noise information, so we use this hack to test the + # behavior of another IndexError that would be raised in the usual case. + rgc.noise_file_name = [ 'none' for i in rgc.ident ] + assert_raises(IndexError, rgc.getNoiseProperties, 5) + + # Now test out the real ones. But if they aren't installed, abort gracefully. + try: + rgc = galsim.RealGalaxyCatalog(sample='25.2') + except OSError: + print('Skipping tests of 25.2 sample, since not downloaded.') + else: + print('sample = ',rgc.sample) + print('len(ident) = ',len(rgc.ident)) + assert(rgc.sample == '25.2') + assert(len(rgc.ident) == 87798) + + try: + rgc = galsim.RealGalaxyCatalog(sample='23.5') + except OSError: + print('Skipping tests of 25.2 sample, since not downloaded.') + else: + print('sample = ',rgc.sample) + print('len(ident) = ',len(rgc.ident)) + assert(rgc.sample == '23.5') + assert(len(rgc.ident) == 56062) + + @timer def test_real_galaxy_ideal(): """Test accuracy of various calculations with fake Gaussian RealGalaxy vs. ideal expectations""" @@ -72,11 +132,15 @@ def test_real_galaxy_ideal(): # or when trying to specify the galaxy too many ways rg_1 = galsim.RealGalaxy(rgc, index = ind_fake, rng = galsim.BaseDeviate(1234)) rg_2 = galsim.RealGalaxy(rgc, random=True) + assert_raises(TypeError, galsim.RealGalaxy, rgc, index=ind_fake, rng='foo') + assert_raises(TypeError, galsim.RealGalaxy, rgc) + assert_raises(TypeError, galsim.RealGalaxy, rgc, index=ind_fake, flux=12, flux_rescale=2) + assert_raises(ValueError, galsim.RealGalaxy, rgc, index=ind_fake, id=0) assert_raises(ValueError, galsim.RealGalaxy, rgc, index=ind_fake, random=True) assert_raises(ValueError, galsim.RealGalaxy, rgc, id=0, random=True) - assert_raises(ValueError, galsim.RealGalaxy, rgc) + # Different RNGs give different random galaxies. rg_3 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(12345)) rg_4 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(67890)) @@ -343,6 +407,14 @@ def test_crg_roundtrip(): im_f814w_mom.observed_shape.g2, rtol=0, atol=1e-4) + # Check some errors + cats = [f606w_cat, f814w_cat] + assert_raises(TypeError, galsim.ChromaticRealGalaxy, real_galaxy_catalogs=cats) + assert_raises(TypeError, galsim.ChromaticRealGalaxy, cats, index=3, id=4) + assert_raises(TypeError, galsim.ChromaticRealGalaxy, cats, index=3, random=True) + assert_raises(TypeError, galsim.ChromaticRealGalaxy, cats, id=4, random=True) + assert_raises(TypeError, galsim.ChromaticRealGalaxy, cats, random=True, rng='foo') + @timer def test_crg_roundtrip_larger_target_psf(): @@ -764,6 +836,7 @@ def test_crg_noise_pad(): if __name__ == "__main__": + test_real_galaxy_catalog() test_real_galaxy_ideal() test_real_galaxy_saved() test_real_galaxy_makeFromImage() From 6f9bfe043270b61d97f8cd8efa4d41e2fc67ee1c Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 22:43:58 -0400 Subject: [PATCH 52/96] Increase test coverage of realgalaxy (#755) --- galsim/real.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/galsim/real.py b/galsim/real.py index 679921236ad..20eeb72860f 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -41,6 +41,7 @@ from .position import PositionD from .utilities import doc_inherit from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimIndexError HST_area = 45238.93416 # Area of HST primary mirror in cm^2 from Synphot User's Guide. @@ -710,7 +711,7 @@ def getGalImage(self, i): if self.logger: self.logger.debug('RealGalaxyCatalog %d: Start getGalImage',i) if i >= len(self.gal_file_name): - raise IndexError( + raise GalSimIndexError( 'index %d given to getGalImage is out of range (0..%d)' % (i,len(self.gal_file_name)-1)) f = self._getFile(self.gal_file_name[i]) @@ -729,7 +730,7 @@ def getPSFImage(self, i): if self.logger: self.logger.debug('RealGalaxyCatalog %d: Start getPSFImage',i) if i >= len(self.psf_file_name): - raise IndexError( + raise GalSimIndexError( 'index %d given to getPSFImage is out of range (0..%d)' % (i,len(self.psf_file_name)-1)) f = self._getFile(self.psf_file_name[i]) @@ -759,7 +760,7 @@ def getNoiseProperties(self, i): im = None else: if i >= len(self.noise_file_name): - raise IndexError( + raise GalSimIndexError( 'index %d given to getNoise is out of range (0..%d)'%( i,len(self.noise_file_name)-1)) if self.noise_file_name[i] in self.saved_noise_im: From 63d7c58738b6cf8881b746dbddf620aafb325d87 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 28 Apr 2018 23:13:30 -0400 Subject: [PATCH 53/96] Increase test coverage of image (#755) --- galsim/fits.py | 39 ++---- galsim/image.py | 20 ++- galsim/photon_array.py | 8 +- galsim/wcs.py | 12 +- tests/test_calc.py | 2 + tests/test_draw.py | 19 +++ tests/test_fitsheader.py | 5 + tests/test_image.py | 270 ++++++++++++++++++++++++++++++++----- tests/test_optics.py | 4 +- tests/test_photon_array.py | 37 +++++ tests/test_sensor.py | 14 +- 11 files changed, 344 insertions(+), 86 deletions(-) diff --git a/galsim/fits.py b/galsim/fits.py index ddb846674fa..386aeeca570 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -214,7 +214,7 @@ def __call__(self, file, dir, file_compress): self.bz2 = self.bz2_methods[self.bz2_index] else: # pragma: no cover raise GalSimError("None of the options for bunzipping were successful.") - else: + else: # pragma: no cover (can't get here from public API) raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) _read_file = _ReadFile() @@ -391,7 +391,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) self.bz2 = self.bz2_methods[self.bz2_index] else: # pragma: no cover raise GalSimError("None of the options for bunzipping were successful.") - else: + else: # pragma: no cover (can't get here from public API) raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) _write_file = _WriteFile() @@ -424,19 +424,11 @@ def _check_hdu(hdu, pyfits_compress): # Check that the specified compression is right for the given hdu type. if pyfits_compress: - if not isinstance(hdu, pyfits.CompImageHDU): # pragma: no cover - if isinstance(hdu, pyfits.BinTableHDU): - raise OSError('Expecting a CompImageHDU, but got a BinTableHDU. Probably your ' - 'pyfits installation does not have the pyfitsComp module installed.') - elif isinstance(hdu, pyfits.ImageHDU): - import warnings - warnings.warn("Expecting a CompImageHDU, but found an uncompressed ImageHDU", - GalSimWarning) - else: - raise OSError('Found invalid HDU reading FITS file (expected an ImageHDU)') + if not isinstance(hdu, pyfits.CompImageHDU): + raise OSError('Found invalid HDU type reading FITS file (expected a CompImageHDU)') else: if not isinstance(hdu, pyfits.ImageHDU) and not isinstance(hdu, pyfits.PrimaryHDU): - raise OSError('Found invalid HDU reading FITS file (expected an ImageHDU)') + raise OSError('Found invalid HDU type reading FITS file (expected an ImageHDU)') def _get_hdu(hdu_list, hdu, pyfits_compress): @@ -455,8 +447,10 @@ def _get_hdu(hdu_list, hdu, pyfits_compress): if len(hdu_list) <= hdu: raise OSError('Expecting at least %d HDUs in galsim.read'%(hdu+1)) hdu = hdu_list[hdu] - else: + elif isinstance(hdu_list, (pyfits.ImageHDU, pyfits.PrimaryHDU, pyfits.CompImageHDU)): hdu = hdu_list + else: + raise TypeError("Invalid hdu_list: %s",hdu_list) _check_hdu(hdu, pyfits_compress) return hdu @@ -651,6 +645,8 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, if image_list.dtype.kind == 'c': raise GalSimValueError("Cannot write complex numpy arrays to a fits file. " "Write array.real and array.imag separately.", image_list) + elif len(image_list) == 0: + raise GalSimValueError("In writeCube: image_list has no images", image_list) elif all(isinstance(item, np.ndarray) for item in image_list): is_all_numpy = True if any(a.dtype.kind == 'c' for a in image_list): @@ -684,8 +680,6 @@ def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, wcs = None else: nimages = len(image_list) - if (nimages == 0): - raise GalSimValueError("In writeCube: image_list has no images", image_list) im = image_list[0] dtype = im.array.dtype nx = im.xmax - im.xmin + 1 @@ -748,6 +742,7 @@ def writeFile(file_name, hdu_list, dir=None, clobber=True, compression='auto'): if pyfits_compress and compression != 'auto': # If compression is auto and it determined that it should use rice, then we # should presume that the hdus were already rice compressed, so we can ignore it here. + # Otherwise, any pyfits_compression options are invalid. raise GalSimValueError("Compression %s is invalid for writeFile",compression) _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress) @@ -973,9 +968,9 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut if file_name: hdu_list, fin = _read_file(file_name, dir, file_compress) - hdu = _get_hdu(hdu_list, hdu, pyfits_compress) - try: + hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + wcs, origin = wcs.readFromFitsHeader(hdu.header) dt = hdu.data.dtype.type if dt in Image.valid_dtypes: @@ -1231,12 +1226,7 @@ def __contains__(self, key): def __delitem__(self, key): self._tag = None - # This is equivalent to the newer pyfits implementation, but older versions silently - # did nothing if the key was not in the header. - if key in self.header: - del self.header[key] - else: - raise KeyError("key %r not in FitsHeader"%(key)) + del self.header[key] def __getitem__(self, key): return self.header[key] @@ -1255,7 +1245,6 @@ def __setitem__(self, key, value): except AttributeError: pass self._tag = None - # Recent versions implement the above logic with the regular setitem method. self.header[key] = value def clear(self): diff --git a/galsim/image.py b/galsim/image.py index b119a7e151f..72f8c8a1a04 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -293,8 +293,6 @@ def __init__(self, *args, **kwargs): if dtype is not None and dtype not in Image.valid_dtypes: raise GalSimValueError("Invlid dtype.", dtype, Image.valid_dtypes) if array is not None: - if not isinstance(array, np.ndarray): - raise TypeError("array must be a numpy.ndarray instance") if copy is None: copy = False if dtype is None: dtype = array.dtype.type @@ -376,7 +374,7 @@ def __init__(self, *args, **kwargs): if init_value is not None: raise GalSimIncompatibleValuesError( "Cannot specify init_value without setting an initial size", - init_value, ncol=ncol, nrow=nrow, bounds=bounds) + init_value=init_value, ncol=ncol, nrow=nrow, bounds=bounds) # Construct the wcs attribute if scale is not None: @@ -879,11 +877,10 @@ def calculate_fft(self): if self.wcs is None: raise GalSimError("calculate_fft requires that the scale be set.") if not self.wcs.isPixelScale(): - raise GalSimValueError("calculate_fft requires that the image has a PixelScale wcs.", - self.wcs) + raise GalSimError("calculate_fft requires that the image has a PixelScale wcs.") if not self.bounds.isDefined(): - raise GalSimUndefinedBoundsError("calculate_fft requires that the image have defined " - "bounds.") + raise GalSimUndefinedBoundsError( + "calculate_fft requires that the image have defined bounds.") No2 = max(-self.bounds.xmin, self.bounds.xmax+1, -self.bounds.ymin, self.bounds.ymax+1) @@ -928,8 +925,7 @@ def calculate_inverse_fft(self): if self.wcs is None: raise GalSimError("calculate_inverse_fft requires that the scale be set.") if not self.wcs.isPixelScale(): - raise GalSimValueError("calculate_inverse_fft requires that the image has a " - "PixelScale wcs.", self.wcs) + raise GalSimError("calculate_inverse_fft requires that the image has a PixelScale wcs.") if not self.bounds.isDefined(): raise GalSimUndefinedBoundsError("calculate_inverse_fft requires that the image have " "defined bounds.") @@ -979,6 +975,8 @@ def copyFrom(self, rhs): """ if self.isconst: raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not isinstance(rhs, Image): + raise TypeError("Trying to copyFrom a non-image") if self.bounds.numpyShape() != rhs.bounds.numpyShape(): raise GalSimIncompatibleValuesError( "Trying to copy images that are not the same shape", self_image=self, rhs=rhs) @@ -1005,7 +1003,7 @@ def view(self, scale=None, wcs=None, origin=None, center=None, make_const=False) if scale is not None: if wcs is not None: - raise GalSimIncompatibleValeusError( + raise GalSimIncompatibleValuesError( "Cannot provide both scale and wcs", scale=scale, wcs=wcs) wcs = PixelScale(scale) elif wcs is not None: @@ -1771,7 +1769,7 @@ def Image_neg(self): # Define &, ^ and | only for integer-type images def Image_and(self, other): - check_image_consistency(self, other) + check_image_consistency(self, other, integer=True) try: a = other.array except AttributeError: diff --git a/galsim/photon_array.py b/galsim/photon_array.py index 96e30523670..3b4949418a8 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -25,7 +25,7 @@ from . import _galsim from .random import UniformDeviate from .angle import radians, arcsec -from .errors import GalSimError, GalSimRangeError +from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError # Add on more methods in the python layer @@ -190,8 +190,8 @@ def scaleFlux(self, scale): self._flux *= scale def scaleXY(self, scale): - self._x *= scale - self._y *= scale + self._x *= float(scale) + self._y *= float(scale) def assignAt(self, istart, rhs): "Assign the contents of another PhotonArray to this one starting at istart." @@ -317,7 +317,7 @@ def makeFromImage(cls, image, max_flux=1., rng=None): photons._y = photons.y[:N] photons._flux = photons.flux[:N] - if image.scale != 1.: + if image.scale != 1. and image.scale is not None: photons.scaleXY(image.scale) return photons diff --git a/galsim/wcs.py b/galsim/wcs.py index ec9faa37cfc..800334f8907 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -1166,7 +1166,7 @@ class PixelScale(LocalWCS): def __init__(self, scale): self._color = None - self._scale = scale + self._scale = float(scale) # Help make sure PixelScale is read-only. @property @@ -1271,7 +1271,7 @@ class ShearWCS(LocalWCS): def __init__(self, scale, shear): self._color = None - self._scale = scale + self._scale = float(scale) self._shear = shear self._g1 = shear.g1 self._g2 = shear.g2 @@ -1407,10 +1407,10 @@ class JacobianWCS(LocalWCS): def __init__(self, dudx, dudy, dvdx, dvdy): self._color = None - self._dudx = dudx - self._dudy = dudy - self._dvdx = dvdx - self._dvdy = dvdy + self._dudx = float(dudx) + self._dudy = float(dudy) + self._dvdx = float(dvdx) + self._dvdy = float(dvdy) self._det = dudx * dvdy - dudy * dvdx # Help make sure JacobianWCS is read-only. diff --git a/tests/test_calc.py b/tests/test_calc.py index 06dce03f1d0..d3a669fd448 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -261,6 +261,8 @@ def test_sigma(): np.testing.assert_almost_equal( test_sigma/e1_sigma, 1.0, decimal=4, err_msg="image.calculateMomentRadius is not accurate.") + with assert_raises(galsim.GalSimValueError): + im.calculateMomentRadius(rtype='invalid') # Check that a non-square image works correctly. Also, not centered anywhere in particular. bounds = galsim.BoundsI(-1234, -1234+size*2, 8234, 8234+size) diff --git a/tests/test_draw.py b/tests/test_draw.py index ecb3c8dff9e..901047254c2 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -1231,6 +1231,25 @@ def test_fft(): im2_real.array, im2_alt_real.array, 9, "inverse_fft produce a different array than obj2.drawImage(nx,ny,method='sb')") + # wcs must be a PixelScale + xim.wcs = galsim.JacobianWCS(1.1,0.1,0.1,1) + with assert_raises(galsim.GalSimError): + xim.calculate_fft() + with assert_raises(galsim.GalSimError): + xim.calculate_inverse_fft() + xim.wcs = None + with assert_raises(galsim.GalSimError): + xim.calculate_fft() + with assert_raises(galsim.GalSimError): + xim.calculate_inverse_fft() + + # inverse needs image with 0,0 + xim.scale=1 + xim.setOrigin(1,1) + with assert_raises(galsim.GalSimBoundsError): + xim.calculate_inverse_fft() + + @timer def test_np_fft(): """Test the equivalence between np.fft functions and the galsim versions diff --git a/tests/test_fitsheader.py b/tests/test_fitsheader.py index 44ee57efa8c..5798863a4f4 100644 --- a/tests/test_fitsheader.py +++ b/tests/test_fitsheader.py @@ -95,6 +95,11 @@ def check_tpv(header): check_tpv(header) do_pickle(header) + assert_raises(TypeError, galsim.FitsHeader, file_name=file_name, header=header) + with pyfits.open(os.path.join(dir,file_name)) as hdu_list: + assert_raises(TypeError, galsim.FitsHeader, file_name=file_name, hdu_list=hdu_list) + assert_raises(TypeError, galsim.FitsHeader, header=header, hdu_list=hdu_list) + # Remove an item from the header # Start with file_name constructor, to test that the repr is changed by the edit. orig_header = header diff --git a/tests/test_image.py b/tests/test_image.py index 73675fca810..42c5b03805d 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -246,29 +246,33 @@ def test_Image_basic(): assert im2_cview[x,y] == value3 # Setting or getting the value outside the bounds should throw an exception. - assert_raises(galsim.GalSimError,im1.setValue,0,0,1) - assert_raises(galsim.GalSimError,im1.__call__,0,0) - assert_raises(galsim.GalSimError,im1.__getitem__,0,0) - assert_raises(galsim.GalSimError,im1.__setitem__,0,0,1) - assert_raises(galsim.GalSimError,im1.view().setValue,0,0,1) - assert_raises(galsim.GalSimError,im1.view().__call__,0,0) - assert_raises(galsim.GalSimError,im1.view().__getitem__,0,0) - assert_raises(galsim.GalSimError,im1.view().__setitem__,0,0,1) - - assert_raises(galsim.GalSimError,im1.setValue,ncol+1,0,1) - assert_raises(galsim.GalSimError,im1.__call__,ncol+1,0) - assert_raises(galsim.GalSimError,im1.view().setValue,ncol+1,0,1) - assert_raises(galsim.GalSimError,im1.view().__call__,ncol+1,0) - - assert_raises(galsim.GalSimError,im1.setValue,0,nrow+1,1) - assert_raises(galsim.GalSimError,im1.__call__,0,nrow+1) - assert_raises(galsim.GalSimError,im1.view().setValue,0,nrow+1,1) - assert_raises(galsim.GalSimError,im1.view().__call__,0,nrow+1) - - assert_raises(galsim.GalSimError,im1.setValue,ncol+1,nrow+1,1) - assert_raises(galsim.GalSimError,im1.__call__,ncol+1,nrow+1) - assert_raises(galsim.GalSimError,im1.view().setValue,ncol+1,nrow+1,1) - assert_raises(galsim.GalSimError,im1.view().__call__,ncol+1,nrow+1) + assert_raises(galsim.GalSimBoundsError,im1.setValue,0,0,1) + assert_raises(galsim.GalSimBoundsError,im1.addValue,0,0,1) + assert_raises(galsim.GalSimBoundsError,im1.__call__,0,0) + assert_raises(galsim.GalSimBoundsError,im1.__getitem__,0,0) + assert_raises(galsim.GalSimBoundsError,im1.__setitem__,0,0,1) + assert_raises(galsim.GalSimBoundsError,im1.view().setValue,0,0,1) + assert_raises(galsim.GalSimBoundsError,im1.view().__call__,0,0) + assert_raises(galsim.GalSimBoundsError,im1.view().__getitem__,0,0) + assert_raises(galsim.GalSimBoundsError,im1.view().__setitem__,0,0,1) + + assert_raises(galsim.GalSimBoundsError,im1.setValue,ncol+1,0,1) + assert_raises(galsim.GalSimBoundsError,im1.addValue,ncol+1,0,1) + assert_raises(galsim.GalSimBoundsError,im1.__call__,ncol+1,0) + assert_raises(galsim.GalSimBoundsError,im1.view().setValue,ncol+1,0,1) + assert_raises(galsim.GalSimBoundsError,im1.view().__call__,ncol+1,0) + + assert_raises(galsim.GalSimBoundsError,im1.setValue,0,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.addValue,0,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.__call__,0,nrow+1) + assert_raises(galsim.GalSimBoundsError,im1.view().setValue,0,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.view().__call__,0,nrow+1) + + assert_raises(galsim.GalSimBoundsError,im1.setValue,ncol+1,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.addValue,ncol+1,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.__call__,ncol+1,nrow+1) + assert_raises(galsim.GalSimBoundsError,im1.view().setValue,ncol+1,nrow+1,1) + assert_raises(galsim.GalSimBoundsError,im1.view().__call__,ncol+1,nrow+1) # Also, setting values in something that should be const assert_raises(galsim.GalSimImmutableError,im1.view(make_const=True).setValue,1,1,1) @@ -304,8 +308,8 @@ def test_Image_basic(): dx = 31 dy = 16 im1.shift(dx,dy) - im2_view.setOrigin( 1+dx , 1+dy ) - im3_view.setCenter( (ncol+1)/2+dx , (nrow+1)/2+dy ) + im2_view.setOrigin(1+dx , 1+dy) + im3_view.setCenter((ncol+1)/2+dx , (nrow+1)/2+dy) shifted_bounds = galsim.BoundsI(1+dx, ncol+dx, 1+dy, nrow+dy) assert im1.bounds == shifted_bounds @@ -323,6 +327,13 @@ def test_Image_basic(): assert im2_view(x+dx,y+dy) == value3 assert im3_view(x+dx,y+dy) == value3 + assert_raises(TypeError, im1.shift, dx) + assert_raises(TypeError, im1.shift, dx=dx) + assert_raises(TypeError, im1.shift, x=dx, y=dy) + assert_raises(TypeError, im1.shift, dx, dy=dy) + assert_raises(TypeError, im1.shift, dx, dy, dy) + assert_raises(TypeError, im1.shift, dx, dy, invalid=True) + # Check picklability do_pickle(im1) do_pickle(im1_view) @@ -402,10 +413,17 @@ def test_undefined_image(): assert im11.array.shape == (1,1) assert im11 == im1 - assert_raises(galsim.GalSimError,im1.setValue,0,0,1) - assert_raises(galsim.GalSimError,im1.__call__,0,0) - assert_raises(galsim.GalSimError,im1.view().setValue,0,0,1) - assert_raises(galsim.GalSimError,im1.view().__call__,0,0) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.setValue, 0, 0, 1) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.__call__, 0, 0) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.view().setValue, 0, 0, 1) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.view().__call__, 0, 0) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.view().addValue, 0, 0, 1) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.fill, 3) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.view().fill, 3) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.invertSelf) + im1.scale = 1. + assert_raises(galsim.GalSimUndefinedBoundsError,im1.calculate_fft) + assert_raises(galsim.GalSimUndefinedBoundsError,im1.calculate_inverse_fft) do_pickle(im1.bounds) do_pickle(im1) @@ -479,6 +497,15 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" read failed reading from filename input.") + assert_raises(ValueError, galsim.fits.read, test_file, compression='invalid') + assert_raises(ValueError, ref_image.write, test_file, compression='invalid') + assert_raises(OSError, galsim.fits.read, test_file, compression='rice') + + assert_raises(TypeError, galsim.fits.read) + assert_raises(TypeError, galsim.fits.read, test_file, hdu_list=hdu) + assert_raises(TypeError, ref_image.write) + assert_raises(TypeError, ref_image.write, file_name=test_file, hdu_list=hdu) + # # Test various compression schemes # @@ -531,7 +558,7 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for auto full-file bzip2") - # Test ric + # Test rice test_file = os.path.join(datadir, "test"+tchar[i]+".fits.fz") test_image = galsim.fits.read(test_file, compression='rice') np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, @@ -690,6 +717,35 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" readMulti failed after using writeFile") + assert_raises(ValueError, galsim.fits.readMulti, test_multi_file, compression='invalid') + assert_raises(ValueError, galsim.fits.writeMulti, image_list, test_multi_file, + compression='invalid') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='invalid') + assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='rice') + assert_raises(OSError, galsim.fits.readFile, test_multi_file, compression='rice') + + assert_raises(TypeError, galsim.fits.readMulti) + assert_raises(TypeError, galsim.fits.readMulti, test_multi_file, hdu_list=hdu) + assert_raises(TypeError, galsim.fits.readMulti, hdu_list=test_multi_file) + assert_raises(TypeError, galsim.fits.writeMulti) + assert_raises(TypeError, galsim.fits.writeMulti, image_list) + assert_raises(TypeError, galsim.fits.writeMulti, image_list, + file_name=test_multi_file, hdu_list=hdu) + + assert_raises(TypeError, galsim.fits.writeFile) + assert_raises(TypeError, galsim.fits.writeFile, image_list) + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='invalid') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='rice') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='gzip_tile') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='hcompress') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + compression='plio') + # # Test various compression schemes # @@ -957,6 +1013,26 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" readCube failed after using writeFile") + assert_raises(ValueError, galsim.fits.readCube, test_cube_file, compression='invalid') + assert_raises(ValueError, galsim.fits.writeCube, image_list, test_cube_file, + compression='invalid') + assert_raises(ValueError, galsim.fits.writeFile, image_list, test_cube_file, + compression='invalid') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='rice') + + assert_raises(TypeError, galsim.fits.readCube) + assert_raises(TypeError, galsim.fits.readCube, test_cube_file, hdu_list=hdu) + assert_raises(TypeError, galsim.fits.readCube, hdu_list=test_cube_file) + assert_raises(TypeError, galsim.fits.writeCube) + assert_raises(TypeError, galsim.fits.writeCube, image_list) + assert_raises(TypeError, galsim.fits.writeCube, image_list, + file_name=test_cube_file, hdu_list=hdu_list) + + assert_raises(ValueError, galsim.fits.writeCube, image_list[:0], test_cube_file) + assert_raises(ValueError, galsim.fits.writeCube, + [image_list[0], image_list[1].subImage(galsim.BoundsI(0,4,0,4))], + test_cube_file) + # # Test various compression schemes # @@ -1131,12 +1207,8 @@ def test_Image_binary_add(): # shape. Note that this test is only included here (not in the unit tests for all # other operations) because all operations have the same error-checking code, so it should # only be necessary to check once. - image1 = galsim.Image(ref_array.astype(types[i])) - image2 = image1.subImage(galsim.BoundsI(image1.xmin, image1.xmax-1, - image1.ymin+1, image1.ymax)) with assert_raises(ValueError): - image1.__add__(image2) - + image1 + image1.subImage(galsim.BoundsI(0,4,0,4)) @timer def test_Image_binary_subtract(): @@ -1170,6 +1242,9 @@ def test_Image_binary_subtract(): err_msg="Inplace add in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 - image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_binary_multiply(): @@ -1210,6 +1285,9 @@ def test_Image_binary_multiply(): err_msg="Inplace add in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 * image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_binary_divide(): @@ -1253,6 +1331,9 @@ def test_Image_binary_divide(): err_msg="Inplace divide in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 / image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_binary_scalar_add(): @@ -1402,6 +1483,9 @@ def test_Image_binary_scalar_pow(): err_msg="Binary pow scalar in Image class (dictionary call) does" +" not match reference for dtype = "+str(types[i])) + with assert_raises(TypeError): + image1 ** image2 + @timer def test_Image_inplace_add(): @@ -1436,6 +1520,9 @@ def test_Image_inplace_add(): err_msg="Inplace add in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 += image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_inplace_subtract(): @@ -1470,6 +1557,9 @@ def test_Image_inplace_subtract(): err_msg="Inplace subtract in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 -= image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_inplace_multiply(): @@ -1504,6 +1594,9 @@ def test_Image_inplace_multiply(): err_msg="Inplace multiply in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 *= image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_inplace_divide(): @@ -1556,6 +1649,9 @@ def test_Image_inplace_divide(): err_msg="Inplace divide in Image class does not match reference for dtypes = " +str(types[i])+" and "+str(types[j])) + with assert_raises(ValueError): + image1 /= image1.subImage(galsim.BoundsI(0,4,0,4)) + @timer def test_Image_inplace_scalar_add(): @@ -1677,6 +1773,8 @@ def test_Image_inplace_scalar_pow(): err_msg="Inplace scalar pow in Image class (dictionary " +"call) does not match reference for dtype = "+str(types[i])) + with assert_raises(TypeError): + image1 **= image2 @timer def test_Image_subImage(): @@ -1770,6 +1868,10 @@ def test_Image_subImage(): do_pickle(image) + assert_raises(TypeError, image.subImage, bounds=None) + assert_raises(TypeError, image.subImage, bounds=galsim.BoundsD(0,4,0,4)) + + def make_subImage(file_name, bounds): """Helper function for test_subImage_persistence """ @@ -1880,11 +1982,14 @@ def test_Image_resize(): do_pickle(im2) do_pickle(im3) + assert_raises(TypeError, im1.resize, bounds=None) + assert_raises(TypeError, im1.resize, bounds=galsim.BoundsD(0,5,0,5)) + @timer def test_ConstImage_array_constness(): """Test that Image instances with make_const=True cannot be modified via their .array - attributes, and that if this is attempted a GalSimError is raised. + attributes, and that if this is attempted a GalSimImmutableError is raised. """ for i in range(ntypes): image = galsim.Image(ref_array.astype(types[i]), make_const=True) @@ -1948,7 +2053,7 @@ def test_BoundsI_init_with_non_pure_ints(): assert ref_bounds == galsim.BoundsI(*bound_arr_flt), \ "Cannot initialize a BoundI with float array elements" - # Using non-integers should raise a ValueError + # Using non-integers should raise a TypeError assert_raises(TypeError, galsim.BoundsI, *bound_arr_flt_nonint) assert_raises(TypeError, galsim.BoundsI, xmin=bound_arr_flt_nonint[0], xmax=bound_arr_flt_nonint[1], @@ -2088,6 +2193,8 @@ def test_Image_view(): imv2.setCenter(0,0) assert imv.bounds == imv2.bounds assert imv.wcs == imv2.wcs + with assert_raises(galsim.GalSimError): + imv.scale # scale is invalid if wcs is not a PixelScale do_pickle(imv) do_pickle(imv2) @@ -2100,6 +2207,8 @@ def test_Image_view(): assert imv(11,19) == 50 assert im(11,19) == 50 imv2 = im.view() + with assert_raises(galsim.GalSimError): + imv2.scale = 0.17 # Invalid if wcs is not PixelScale imv2.wcs = None imv2.scale = 0.17 assert imv.bounds == imv2.bounds @@ -2127,6 +2236,11 @@ def test_Image_view(): assert im.array.min() == 17 assert im.array.max() == 17 + assert_raises(TypeError, im.view, origin=(0,0), center=(0,0)) + assert_raises(TypeError, im.view, scale=0.3, wcs=galsim.JacobianWCS(1.1, 0.1, 0.1, 1.)) + assert_raises(TypeError, im.view, scale=galsim.PixelScale(0.3)) + assert_raises(TypeError, im.view, wcs=0.3) + @timer def test_Image_writeheader(): @@ -2279,6 +2393,10 @@ def test_copy(): im8[3,8] = 15 assert im5(3,8) == 11. + assert_raises(TypeError, im5.copyFrom, im8.array) + im9 = galsim.Image(5,5,init_value=3) + assert_raises(ValueError, im5.copyFrom, im9) + @timer def test_complex_image(): @@ -2800,6 +2918,69 @@ def test_int_image_arith(): np.testing.assert_array_equal(test.array, 0, err_msg="//= failed for Images with dtype = %s."%types[i]) + with assert_raises(ValueError): + full & full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full | full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full ^ full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full // full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full % full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full &= full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full |= full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full ^= full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full //= full.subImage(galsim.BoundsI(0,4,0,4)) + with assert_raises(ValueError): + full %= full.subImage(galsim.BoundsI(0,4,0,4)) + + imd = galsim.ImageD(ref_array) + with assert_raises(ValueError): + imd & full + with assert_raises(ValueError): + imd | full + with assert_raises(ValueError): + imd ^ full + with assert_raises(ValueError): + imd // full + with assert_raises(ValueError): + imd % full + with assert_raises(ValueError): + imd &= full + with assert_raises(ValueError): + imd |= full + with assert_raises(ValueError): + imd ^= full + with assert_raises(ValueError): + imd //= full + with assert_raises(ValueError): + imd %= full + + with assert_raises(ValueError): + full & imd + with assert_raises(ValueError): + full | imd + with assert_raises(ValueError): + full ^ imd + with assert_raises(ValueError): + full // imd + with assert_raises(ValueError): + full % imd + with assert_raises(ValueError): + full &= imd + with assert_raises(ValueError): + full |= imd + with assert_raises(ValueError): + full ^= imd + with assert_raises(ValueError): + full //= imd + with assert_raises(ValueError): + full %= imd @timer @@ -2926,6 +3107,21 @@ def test_wrap(): np.testing.assert_equal(im3_wrap.bounds, b3, "image.wrap(%s) does not have the correct bounds") + b = galsim.BoundsI(-K+1,K,-L+1,L) + b2 = galsim.BoundsI(-K+1,K,0,L) + b3 = galsim.BoundsI(0,K,-L+1,L) + assert_raises(TypeError, im.wrap, bounds=None) + assert_raises(ValueError, im3.wrap, b, hermitian='x') + assert_raises(ValueError, im3.wrap, b2, hermitian='x') + assert_raises(ValueError, im.wrap, b3, hermitian='x') + assert_raises(ValueError, im2.wrap, b, hermitian='y') + assert_raises(ValueError, im2.wrap, b3, hermitian='y') + assert_raises(ValueError, im.wrap, b2, hermitian='y') + assert_raises(ValueError, im.wrap, b, hermitian='invalid') + assert_raises(ValueError, im2.wrap, b2, hermitian='invalid') + assert_raises(ValueError, im3.wrap, b3, hermitian='invalid') + + @timer def test_FITS_bad_type(): """Test that reading FITS files with an invalid data type succeeds by converting the diff --git a/tests/test_optics.py b/tests/test_optics.py index 677666cda69..7d8e5ff383c 100644 --- a/tests/test_optics.py +++ b/tests/test_optics.py @@ -417,7 +417,7 @@ def test_OpticalPSF_pupil_plane(): # need it, and it is invalid to give lam_over_diam (rather than lam, diam separately) when # there is a specific scale for the pupil plane image. But see the last test below where # we do use lam, diam separately with the input image. - im.scale = None + im.wcs = None # This implies that the lam_over_diam value test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, oversampling=pp_oversampling, pupil_plane_im=im, @@ -574,7 +574,7 @@ def test_OpticalPSF_pupil_plane(): calculate_maxk=False, calculate_stepk=False, x_interpolant='linear') new_im = int_im.drawImage(scale=rescale_fac, method='no_pixel') - new_im.scale = None # Let OpticalPSF figure out the scale automatically. + new_im.wcs = None # Let OpticalPSF figure out the scale automatically. test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=new_im, oversampling=pp_oversampling, gsparams=gsp) diff --git a/tests/test_photon_array.py b/tests/test_photon_array.py index d94f7fa0cd5..fc034c26c9e 100644 --- a/tests/test_photon_array.py +++ b/tests/test_photon_array.py @@ -181,6 +181,40 @@ def test_photon_array(): np.testing.assert_almost_equal(pa2.dydz[50:], pa1.dydz) np.testing.assert_almost_equal(pa2.wavelength[50:], pa1.wavelength) + # Error if it doesn't fit. + assert_raises(ValueError, pa2.assignAt, 90, pa1) + + # Test some trivial usage of makeFromImage + zero = galsim.Image(4,4,init_value=0) + photons = galsim.PhotonArray.makeFromImage(zero) + print('photons = ',photons) + assert len(photons) == 16 + np.testing.assert_array_equal(photons.flux, 0.) + + ones = galsim.Image(4,4,init_value=1) + photons = galsim.PhotonArray.makeFromImage(ones) + print('photons = ',photons) + assert len(photons) == 16 + np.testing.assert_almost_equal(photons.flux, 1.) + + tens = galsim.Image(4,4,init_value=8) + photons = galsim.PhotonArray.makeFromImage(tens, max_flux=5.) + print('photons = ',photons) + assert len(photons) == 32 + np.testing.assert_almost_equal(photons.flux, 4.) + + assert_raises(ValueError, galsim.PhotonArray.makeFromImage, zero, max_flux=0.) + assert_raises(ValueError, galsim.PhotonArray.makeFromImage, zero, max_flux=-2) + + # Check some other errors + undef = galsim.Image() + assert_raises(galsim.GalSimUndefinedBoundsError, pa2.addTo, undef) + + # This shouldn't be able to happen in regular photon-shooting usage, so check here. + # TODO: Would be nice to have some real tests of the convolve functionality here, + # rather than just implicitly in the shooting tests. + assert_raises(galsim.GalSimError, pa2.convolve, pa1) + # Check picklability again with non-zero values for everything do_pickle(photon_array) @@ -540,6 +574,9 @@ def test_dcr(): parallactic_angle=parallactic_angle, scale_unit='inches') # invalid scale_unit + # Invalid to use dcr without some way of setting wavelengths. + assert_raises(galsim.GalSimError, achrom.drawImage, im2, method='phot', surface_ops=[dcr]) + @unittest.skipIf(no_astroplan, 'Unable to import astroplan') @timer def test_dcr_angles(): diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 6f524cd980d..3f6da9ce366 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -297,6 +297,15 @@ def test_silicon(): assert_raises(TypeError, galsim.SiliconSensor, rng=3.4) assert_raises(TypeError, galsim.SiliconSensor, 'lsst_itl_8', rng1) + # Invalid to accumulate onto undefined image. + photons = galsim.PhotonArray(3) + image = galsim.ImageD() + with assert_raises(galsim.GalSimUndefinedBoundsError): + simple.accumulate(photons, image) + with assert_raises(galsim.GalSimUndefinedBoundsError): + silicon.accumulate(photons, image) + + @timer def test_silicon_fft(): """Test that drawing with method='fft' also works for SiliconSensor. @@ -740,6 +749,10 @@ def test_treerings(): np.testing.assert_almost_equal(ref_mom['My'] + treering_amplitude * center[1] / 1000, mom['My'], decimal=1) + assert_raises(TypeError, galsim.SiliconSensor, treering_func=lambda x:np.cos(x)) + assert_raises(TypeError, galsim.SiliconSensor, treering_func=tr7, treering_center=(3,4)) + + @timer def test_resume(): """Test that the resume option for accumulate works properly. @@ -976,7 +989,6 @@ def test_flat(): np.testing.assert_allclose(cov20 / counts_total, 0., atol=2*toler) np.testing.assert_allclose(cov02 / counts_total, 0., atol=2*toler) - if __name__ == "__main__": test_simple() test_silicon() From 44c9cb068deb0e4cf3ee3c93991ec7c2442134f9 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 29 Apr 2018 00:59:36 -0400 Subject: [PATCH 54/96] Increase test coverage of table (#755) --- galsim/table.py | 9 ++------- tests/test_table.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/galsim/table.py b/galsim/table.py index c19e137225d..6fbab3ca207 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -166,9 +166,6 @@ def __call__(self, x): # Handle the log(x) if necessary if self.x_log: - if np.any(np.asarray(x) <= 0.): - raise GalSimValueError("Cannot interpolate x<=0 when using log(x) interpolation.", - x) x = np.log(x) x = np.asarray(x) @@ -176,9 +173,7 @@ def __call__(self, x): f = self._tab.interp(float(x)) else: dimen = len(x.shape) - if dimen > 2: - raise GalSimValueError("Arrays with dimension larger than 2 not allowed.", x) - elif dimen == 2: + if dimen > 1: f = np.empty_like(x.ravel(), dtype=float) xx = x.astype(float,copy=False).ravel() self._tab.interpMany(xx.ctypes.data, f.ctypes.data, len(xx)) @@ -281,7 +276,7 @@ def from_file(cls, file_name, interpolant='spline', x_log=False, f_log=False, am data = data.values.transpose() except (ImportError, AttributeError, CParserError): # pragma: no cover data = np.loadtxt(file_name).transpose() - if data.shape[0] != 2: + if data.shape[0] != 2: # pragma: no cover raise GalSimValueError("File provided for LookupTable does not have 2 columns", file_name) x=data[0] diff --git a/tests/test_table.py b/tests/test_table.py index 17c6c6a8f8d..21187a7bcbb 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -121,6 +121,15 @@ def test_table(): do_pickle(table1) do_pickle(table2) + assert_raises(ValueError, galsim.LookupTable, x=args1, f=vals1, interpolant='invalid') + assert_raises(ValueError, galsim.LookupTable, x=[1], f=[1], interpolant='linear') + assert_raises(ValueError, galsim.LookupTable, x=[1,2], f=[1,2], interpolant='spline') + assert_raises(ValueError, galsim.LookupTable, x=[1,1,1], f=[1,2,3]) + assert_raises(ValueError, galsim.LookupTable, x=[0,1,2], f=[1,2,3], x_log=True) + assert_raises(ValueError, galsim.LookupTable, x=[-1,0,1], f=[1,2,3], x_log=True) + assert_raises(ValueError, galsim.LookupTable, x=[0,1,2], f=[0,1,2], f_log=True) + assert_raises(ValueError, galsim.LookupTable, x=[0,1,2], f=[2,-1,2], f_log=True) + @timer def test_init(): @@ -171,6 +180,15 @@ def test_log(): result_4, result_1, decimal=3, err_msg='Disagreement when interpolating in log(f)') + with assert_raises(galsim.GalSimRangeError): + tab_2(-1) + with assert_raises(galsim.GalSimRangeError): + tab_3(-1) + with assert_raises(galsim.GalSimRangeError): + tab_2(x_neg) + with assert_raises(galsim.GalSimRangeError): + tab_3(x_neg) + # Check picklability do_pickle(tab_1) do_pickle(tab_2) @@ -341,11 +359,13 @@ def f(x_, y_): # Test edge exception with assert_raises(ValueError): tab2d(1e6, 1e6) + with assert_raises(ValueError): + tab2d.gradient(1e6, 1e6) # Test edge wrapping # Check that can't construct table with edge-wrapping if edges don't match with assert_raises(ValueError): - galsim.LookupTable((x, y, z), dict(edge_mode='wrap')) + galsim.LookupTable2D(x, y, z, edge_mode='wrap') # Extend edges and make vals match x = np.append(x, x[-1] + (x[-1]-x[-2])) @@ -366,7 +386,8 @@ def f(x_, y_): # Test floor/ceil/nearest interpolant - x = y = np.arange(5) + x = np.arange(5) + y = np.arange(5) z = x + y[:, np.newaxis] tab2d = galsim.LookupTable2D(x, y, z, interpolant='ceil') assert tab2d(2.4, 3.6) == 3+4, "Ceil interpolant failed." @@ -375,6 +396,10 @@ def f(x_, y_): tab2d = galsim.LookupTable2D(x, y, z, interpolant='nearest') assert tab2d(2.4, 3.6) == 2+4, "Nearest interpolant failed." + assert_raises(ValueError, galsim.LookupTable2D, x, y, z, interpolant='invalid') + assert_raises(ValueError, galsim.LookupTable2D, x, y, z, edge_mode='invalid') + assert_raises(ValueError, galsim.LookupTable2D, x, y, z[:-1,:-1]) + # Test that x,y arrays need to be strictly increasing. x[0] = x[1] assert_raises(ValueError, galsim.LookupTable2D, x, y, z) From 094e0a5dcc943547a37015444702a883a3a731fe Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 29 Apr 2018 11:20:43 -0400 Subject: [PATCH 55/96] Increase test coverage of lensing (#755) --- galsim/lensing_ps.py | 15 ++++----------- galsim/utilities.py | 11 +++++++---- tests/test_lensing.py | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/test_pse.py | 11 +++++++++++ 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index 847e2b02ef0..c768a0303a5 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -767,8 +767,8 @@ def _wrap_image(self, im, border=7): Utility function to wrap an input image with some number of border pixels. By default, the number of border pixels is 7, but this function works as long as it's less than the size of the input image itself. This function is used for periodic interpolation by the - getShear() and other methods, but eventually if we make a 2d LookupTable-type class, this - should become a method of that class. + getShear() and other methods, but eventually if we upgrade LookupTable2D to allow + Lanczos interpolation, we should ust that. cf. Issue #751 """ # We should throw an exception if the image is smaller than 'border', since at this point # this process doesn't make sense. @@ -1478,13 +1478,6 @@ def _generate_power_array(self, power_function): # Fudge the value at k=0, so we don't have to evaluate power there k[0,0] = k[1,0] - # Raise a clear exception for LookupTable that are not defined on the full k range! - if isinstance(power_function, LookupTable): - mink = np.min(k) - maxk = np.max(k) - if mink < power_function.x_min or maxk > power_function.x_max: - raise GalSimRangeError("LookupTable P(k) is not defined for full k range on grid", - k, mink, maxk) P_k = np.empty_like(k) P_k[:,:] = power_function(k) @@ -1537,8 +1530,8 @@ def kappaKaiserSquires(g1, g2): prior to input. """ # Checks on inputs - if not isinstance(g1, np.ndarray) and isinstance(g2, np.ndarray): - raise TypeError("Input g1 and g2 must be galsim Image or NumPy arrays.") + if not (isinstance(g1, np.ndarray) and isinstance(g2, np.ndarray)): + raise TypeError("Input g1 and g2 must be NumPy arrays.") if g1.shape != g2.shape: raise GalSimIncompatibleValuesError("Input g1 and g2 must be the same shape.", g1=g1, g2=g2) if g1.shape[0] != g1.shape[1]: diff --git a/galsim/utilities.py b/galsim/utilities.py index 7a9fe3f844e..06e854e22d2 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -266,16 +266,19 @@ def _convertPositions(pos, units, func): This is used by the functions getShear(), getConvergence(), getMagnification(), and getLensing() for both PowerSpectrum and NFWHalo. """ - from .position import PositionD, PositionI + from .position import Position from .angle import AngleUnit, arcsec # Check for PositionD or PositionI: - if isinstance(pos, PositionD) or isinstance(pos, PositionI): + if isinstance(pos, Position): pos = [ pos.x, pos.y ] - # Check for list of PositionD or PositionI: + elif len(pos) == 0: + raise TypeError("Unable to parse the input pos argument for %s."%func) + + # Check for list of Position: # The only other options allow pos[0], so if this is invalid, an exception # will be raised: - elif isinstance(pos[0], PositionD) or isinstance(pos[0], PositionI): + elif isinstance(pos[0], Position): pos = [ np.array([p.x for p in pos], dtype='float'), np.array([p.y for p in pos], dtype='float') ] diff --git a/tests/test_lensing.py b/tests/test_lensing.py index acde177d197..501c1b1c73a 100644 --- a/tests/test_lensing.py +++ b/tests/test_lensing.py @@ -212,6 +212,12 @@ def test_cosmology(): do_pickle(halo2) assert halo == halo2 + assert_raises(ValueError, cosmo.Da, -0.1) + assert_raises(ValueError, cosmo.Da, 2.1, 2.3) + assert_raises(TypeError, galsim.NFWHalo, 1e15, 4, 1, cosmo=5) + assert_raises(TypeError, galsim.NFWHalo, 1e15, 4, 1, cosmo=cosmo, omega_m=wm) + assert_raises(TypeError, galsim.NFWHalo, 1e15, 4, 1, cosmo=cosmo, omega_lam=wl) + @timer def test_shear_variance(): @@ -631,6 +637,13 @@ def test_shear_get(): # build the grid grid_spacing = 17. ngrid = 100 + + # Before calling buildGrid, these are invalid + assert_raises(galsim.GalSimError, my_ps.getShear, galsim.PositionD(0,0)) + assert_raises(galsim.GalSimError, my_ps.getConvergence, galsim.PositionD(0,0)) + assert_raises(galsim.GalSimError, my_ps.getMagnification, galsim.PositionD(0,0)) + assert_raises(galsim.GalSimError, my_ps.getLensing, galsim.PositionD(0,0)) + g1, g2, kappa = my_ps.buildGrid(grid_spacing = grid_spacing, ngrid = ngrid, get_convergence = True) min = (-ngrid/2 + 0.5) * grid_spacing @@ -842,6 +855,13 @@ def test_tabulated(): assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.), f_log=True) assert_raises(ValueError, galsim.LookupTable, (0.,1.,2.), (0.,1.,2.), x_log=True, f_log=True) + # Negative power is invalid. + neg_power = galsim.LookupTable(k_arr, np.cos(k_arr)) + print('neg_power = ',neg_power) + with assert_raises(galsim.GalSimError): + negps = galsim.PowerSpectrum(neg_power) + negps.buildGrid(grid_spacing=1.7, ngrid=10) + # Check some invalid PowerSpectrum parameters assert_raises(ValueError, galsim.PowerSpectrum) assert_raises(ValueError, galsim.PowerSpectrum, delta2=True) @@ -860,6 +880,13 @@ def test_tabulated(): assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10, units='inches') assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10, units=True) assert_raises(ValueError, ps_tab.buildGrid, grid_spacing=1.7, ngrid=10, bandlimit='none') + assert_raises(TypeError, ps_tab.getShear) + assert_raises(TypeError, ps_tab.getShear, pos=()) + assert_raises(TypeError, ps_tab.getShear, pos=3) + assert_raises(TypeError, ps_tab.getShear, pos=(3,)) + assert_raises(TypeError, ps_tab.getShear, pos=(3,4,5)) + assert_raises(ValueError, ps_tab.getShear, pos=(3,4), units='invalid') + assert_raises(ValueError, ps_tab.getShear, pos=(3,4), units=17) # check that when calling LookupTable, you can provide a scalar, list, tuple or array tab = galsim.LookupTable(k_arr, p_arr) @@ -950,6 +977,11 @@ def test_kappa_gauss(): err_msg="Reconstructed kappaE is non-zero at greater than 3 decimal places for rotated "+ "shear field.") + assert_raises(TypeError, galsim.lensing_ps.kappaKaiserSquires, g1=0.3, g2=0.1) + assert_raises(ValueError, galsim.lensing_ps.kappaKaiserSquires, g1=g1, g2=g2[:50,:50]) + assert_raises(NotImplementedError, galsim.lensing_ps.kappaKaiserSquires, + g1=g1[:,:50], g2=g2[:,:50]) + @timer def test_power_spectrum_with_kappa(): @@ -1286,6 +1318,13 @@ def test_periodic(): np.testing.assert_almost_equal(np.var(mu_shift), np.var(mu), decimal=8, err_msg='Magnification variance altered by periodic interpolation') + # If image is too small, can't use periodic boundaries. + ps.buildGrid(ngrid=5, grid_spacing=0.1, units=galsim.degrees, + rng=galsim.UniformDeviate(314159), interpolant='nearest', + kmin_factor=3., kmax_factor=1., get_convergence=True) + with assert_raises(galsim.GalSimError): + ps.getShear(pos=(x.flatten(),y.flatten()), units=galsim.degrees, + reduced=False, periodic=True) @timer def test_bandlimit(): @@ -1336,6 +1375,9 @@ def test_psr(): galsim.lensing_ps.PowerSpectrumRealizer(100, 0.005, pb, pe)] all_obj_diff(diff_psr_list) + with assert_raises(TypeError): + psr(gd=galsim.BaseDeviate(1234)) + @timer def test_normalization(): diff --git a/tests/test_pse.py b/tests/test_pse.py index b7e698bb23e..e5e6825b6c5 100644 --- a/tests/test_pse.py +++ b/tests/test_pse.py @@ -175,6 +175,17 @@ def test_PSE_weight(): assert_raises(TypeError, pse.estimate, g1, g2, weight_EE=8) assert_raises(TypeError, pse.estimate, g1, g2, weight_BB='yes') + # If N is fairly small, then can get zeros in the counts, which raises an error + array_size = 5 + g1, g2 = ps.buildGrid(grid_spacing=grid_spacing, ngrid=array_size, units=galsim.degrees, + rng=galsim.BaseDeviate(rand_seed)) + pse = galsim.pse.PowerSpectrumEstimator(N=array_size, + sky_size_deg=array_size*grid_spacing, + nbin=n_ell) + with assert_raises(galsim.GalSimError): + pse.estimate(g1,g2) + + if __name__ == "__main__": test_PSE_basic() From e5d916235f222846b97cc44cc1dbc88492e466e6 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 22:44:30 -0400 Subject: [PATCH 56/96] Use convert_cpp_errors() context to convert C++ errors into GalSimErrors (#755) --- galsim/airy.py | 5 +- galsim/box.py | 7 ++- galsim/cdmodel.py | 9 ++-- galsim/convolve.py | 14 +++-- galsim/deltafunction.py | 4 +- galsim/errors.py | 105 +++++++++++++++++++++++++++--------- galsim/exponential.py | 5 +- galsim/fft.py | 17 +++--- galsim/fitswcs.py | 21 ++++---- galsim/fouriersqrt.py | 5 +- galsim/gaussian.py | 5 +- galsim/gsobject.py | 5 +- galsim/gsparams.py | 4 +- galsim/hsm.py | 21 ++++---- galsim/image.py | 26 +++++---- galsim/inclined.py | 24 +++++---- galsim/integ.py | 5 +- galsim/interpolant.py | 23 +++++--- galsim/interpolatedimage.py | 30 ++++++----- galsim/kolmogorov.py | 5 +- galsim/lensing_ps.py | 1 - galsim/moffat.py | 9 ++-- galsim/phase_psf.py | 1 - galsim/photon_array.py | 6 ++- galsim/random.py | 20 ++++--- galsim/randwalk.py | 12 +++-- galsim/real.py | 26 ++++----- galsim/second_kick.py | 15 ++++-- galsim/sensor.py | 13 ++--- galsim/sersic.py | 14 +++-- galsim/shapelet.py | 22 ++++---- galsim/spergel.py | 11 ++-- galsim/sum.py | 6 ++- galsim/table.py | 14 ++--- galsim/transform.py | 6 +-- galsim/vonkarman.py | 20 ++++--- 36 files changed, 334 insertions(+), 202 deletions(-) diff --git a/galsim/airy.py b/galsim/airy.py index 26b50f30296..3abc2996792 100644 --- a/galsim/airy.py +++ b/galsim/airy.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimIncompatibleValuesError +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors class Airy(GSObject): @@ -146,7 +146,8 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, obscuration=0., flux @lazy_property def _sbp(self): - return _galsim.SBAiry(self._lod, self._obscuration, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAiry(self._lod, self._obscuration, self._flux, self.gsparams._gsp) @property def lam_over_diam(self): return self._lod diff --git a/galsim/box.py b/galsim/box.py index 8ec8ec147b5..13c60cf8666 100644 --- a/galsim/box.py +++ b/galsim/box.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD +from .errors import convert_cpp_errors class Box(GSObject): @@ -67,7 +68,8 @@ def __init__(self, width, height, flux=1., gsparams=None): @lazy_property def _sbp(self): - return _galsim.SBBox(self._width, self._height, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBBox(self._width, self._height, self._flux, self.gsparams._gsp) @property def width(self): return self._width @@ -225,7 +227,8 @@ def __init__(self, radius, flux=1., gsparams=None): @lazy_property def _sbp(self): - return _galsim.SBTopHat(self._radius, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBTopHat(self._radius, self._flux, self.gsparams._gsp) @property def radius(self): return self._radius diff --git a/galsim/cdmodel.py b/galsim/cdmodel.py index f2ca1d59ba7..13f28bf72f0 100644 --- a/galsim/cdmodel.py +++ b/galsim/cdmodel.py @@ -27,7 +27,7 @@ from .image import Image from . import _galsim -from .errors import GalSimValueError +from .errors import GalSimValueError, convert_cpp_errors class BaseCDModel(object): """Base class for the most generic, i.e. no with symmetries or distance scaling relationships @@ -94,9 +94,10 @@ def applyForward(self, image, gain_ratio=1.): flat and science images have the same gain value """ ret = image.copy() - _galsim._ApplyCD( - ret._image, image._image, self.a_l._image, self.a_r._image, self.a_b._image, - self.a_t._image, int(self.n), float(gain_ratio)) + with convert_cpp_errors(): + _galsim._ApplyCD( + ret._image, image._image, self.a_l._image, self.a_r._image, self.a_b._image, + self.a_t._image, int(self.n), float(gain_ratio)) return ret def applyBackward(self, image, gain_ratio=1.): diff --git a/galsim/convolve.py b/galsim/convolve.py index 28aec020dd3..1d04fa09c7b 100644 --- a/galsim/convolve.py +++ b/galsim/convolve.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject, ChromaticConvolution from .utilities import lazy_property, doc_inherit -from .errors import GalSimWarning, GalSimError +from .errors import GalSimWarning, GalSimError, convert_cpp_errors def Convolve(*args, **kwargs): """A function for convolving 2 or more GSObject or ChromaticObject instances. @@ -210,7 +210,8 @@ def real_space(self): return self._real_space @lazy_property def _sbp(self): SBList = [obj._sbp for obj in self.obj_list] - return _galsim.SBConvolve(SBList, self._real_space, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBConvolve(SBList, self._real_space, self.gsparams._gsp) @lazy_property def _noise(self): @@ -472,7 +473,8 @@ def __init__(self, obj, gsparams=None): @lazy_property def _sbp(self): - return _galsim.SBDeconvolve(self.orig_obj._sbp, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBDeconvolve(self.orig_obj._sbp, self.gsparams._gsp) @property def orig_obj(self): return self._orig_obj @@ -669,7 +671,8 @@ def __init__(self, obj, real_space=None, gsparams=None): @lazy_property def _sbp(self): - return _galsim.SBAutoConvolve(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAutoConvolve(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) @property def orig_obj(self): return self._orig_obj @@ -812,7 +815,8 @@ def __init__(self, obj, real_space=None, gsparams=None): @lazy_property def _sbp(self): - return _galsim.SBAutoCorrelate(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAutoCorrelate(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) @property def orig_obj(self): return self._orig_obj diff --git a/galsim/deltafunction.py b/galsim/deltafunction.py index 0e747fc57d0..c7ad6ae3c5f 100644 --- a/galsim/deltafunction.py +++ b/galsim/deltafunction.py @@ -24,6 +24,7 @@ from .gsparams import GSParams from .position import PositionD from .utilities import doc_inherit +from .errors import convert_cpp_errors class DeltaFunction(GSObject): @@ -71,7 +72,8 @@ def __init__(self, flux=1., gsparams=None): @property def _sbp(self): # NB. I only need this until compound and transform are reimplemented in Python... - return _galsim.SBDeltaFunction(self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBDeltaFunction(self._flux, self.gsparams._gsp) def __eq__(self, other): return (isinstance(other, DeltaFunction) and diff --git a/galsim/errors.py b/galsim/errors.py index 64a19b2bca4..eda1b98f5ed 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -20,28 +20,30 @@ # obviously one of the standard python errors. from builtins import super +from contextlib import contextmanager # Note to developers about which exception to throw. # # Aside from the below classes, which should be preferred for most errors, we also # throw the following in some cases. # -# TypeError: Use this for errors that in a more strongly typed language would probably -# be a compiler error. For instance, it is used for the following errors: -# - a parameter has the wrong type -# - the wrong number of unnamed args when processing `*args` by hand. -# - missing or invalid kwargs when processing `**kwargs` by hand. +# TypeError: Use this for errors that in a more strongly typed language would probably +# be a compiler error. For instance, it is used for the following errors: +# - a parameter has the wrong type +# - the wrong number of unnamed args when processing `*args` by hand. +# - missing or invalid kwargs when processing `**kwargs` by hand. # -# OSError: Use this for errors related to I/O, disk access, etc. Note: In Python 2, -# there was a distinction between IOError and OSError, but there was never much -# difference in reality, and in Python 3, they made everything OSError. -# We should just use OSError for all such kinds of errors. +# OSError: Use this for errors related to I/O, disk access, etc. Note: In Python 2, +# there was a distinction between IOError and OSError, but there was never +# much difference in reality, and in Python 3, they made everything OSError. +# We should just use OSError for all such kinds of errors. # -# KeyError: Use this for the equivalent of accessing a dict-like object with an invalid key. -# E.g. FitsHeader and Catalog raise this for accessing invalid columns. +# KeyError: Use this for the equivalent of accessing a dict-like object with an invalid +# key. E.g. FitsHeader and Catalog raise this for accessing invalid columns. # -# IndexError: Use this for the equivalent of accessing a list-like object with an invalid index. -# E.g. RealGalaxyCatalog and Catalog raise this for accessing invalid rows. +# IndexError: Use this for the equivalent of accessing a list-like object with an invalid +# index. E.g. RealGalaxyCatalog and Catalog raise this for accessing invalid +# rows. # # NotImplementedError: Use this for features that we have not implemented. Even if there is # no future intent to do so. E.g. GSObject defines uses this for a number @@ -52,22 +54,66 @@ # valid for derived classes. E.g. GSObject and Position use this for their # __init__ implementations. # -# AttributeError: Use this only for an attempt to access an attribute that an object does not -# have. We don't currently raise this anywhere in GalSim. +# AttributeError: Use this only for an attempt to access an attribute that an object does not +# have. We don't currently raise this anywhere in GalSim. # -# RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. +# RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. # -# ValueError: Don't use this. Use one of the below exceptions that derive from ValueError. +# ValueError: Don't use this. Use one of the below exceptions that derive from +# ValueError. # -# std::runtime_error: Use this for errors in the C++ layer, and put a try/except guard around -# the C++ call in the Python layer to convert to a GalSimError. E.g. -# GSFitsWCS._invert_pv uses this for non-convergence, but we convert to -# a GalSimError in Python. -# When possible, try to guard against any such events by making appropriate -# checks in the Python layer before dropping down into C++. E.g. Image -# checks for anything that might cause the C++ Image class to throw an -# exception and raises some kind of GalSim exception first. - +# std::runtime_error: Use this for errors in the C++ layer, and use the catch_cpp_errors() +# context to convert these errors into GalSimErrors. E.g. +# GSFitsWCS._invert_pv uses this for non-convergence, which is converted +# into a GalSimError in Python. +# When possible, it is preferable to guard against any such events by making +# appropriate checks in the Python layer before dropping down into C++. +# E.g. Image checks for anything that might cause the C++ Image class to +# throw an exception and raises some kind of GalSim exception first. +# Nonetheless, it is good practice to use the `with convert_cpp_errors()` +# context for all calls to the C++ layer, just in case. +# +# GalSim-specific error classes: +# ------------------------------ +# +# GalSimError: Use this for what would normally be a RuntimeError. Usually some +# exceptional occurrence in otherwise correct code. E.g. an algorithm not +# converging, or some invalid data values. This is also the catch-all +# exception to use when none of the other GalSim exceptions are appropriate. +# +# GalSimValueError: Use this for when a user provides an invalid value for a parameter. +# Note: it has an optional argument to give a list of allowed values when +# that is appropriate. +# +# GalSimRangeError: Use this when a a user provides an value outside of some allowed range. +# You should also give the min/max values of the allowed range. The max +# is optional, because it's not uncommon for their to be no upper limit. +# If only the upper limit is relevant and not the lower limit, you may +# use min=None to indicate this. +# +# GalSimBoundsError: Use this when a position is outside the allowed bounds. It's basically +# the same as GalSimRangeError, but in two dimensions. +# +# GalSimUndefinedBoundsError: Use this when the user tries to performa an operation on an +# Image with undefined bounds that requires the bounds to be +# defined. +# +# GalSimImmutableError: Use this when the user tries to modify an immutable Image in some way. +# +# GalSimIncompatibleValuesError: Use this when two or more parameters are invalid when used +# in combination. E.g. providing more than one size parameter +# to Moffat, Sersic, Gaussian, etc. +# +# GalSimSEDError: Use this when an SED is required to be either spectral or dimensionless +# and the wrong kind of SED is provided. +# +# GalSimHSMError: Use this for errors from the HSM algorithm. +# +# GalSimConfigError: Use this for errors processing a config dict. +# +# GalSimConfigValueERror: Use this when a config dict has a value that is invalid. Basically, +# whenever you would normally use GalSimValueError when processing +# a config dict, you should use this instead. class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. @@ -282,3 +328,10 @@ class GalSimDeprecationWarning(GalSimWarning): """A GalSim-specific warning class used for deprecation warnings. """ def __repr__(self): return 'galsim.GalSimDeprecationWarning(%r)'%(str(self)) + +@contextmanager +def convert_cpp_errors(error_type=GalSimError): + try: + yield + except RuntimeError as err: + raise error_type(str(err)) diff --git a/galsim/exponential.py b/galsim/exponential.py index 9091fe2e1c1..54827c14b7c 100644 --- a/galsim/exponential.py +++ b/galsim/exponential.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimIncompatibleValuesError +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors class Exponential(GSObject): @@ -93,7 +93,8 @@ def __init__(self, half_light_radius=None, scale_radius=None, flux=1., gsparams= @lazy_property def _sbp(self): - return _galsim.SBExponential(self._r0, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBExponential(self._r0, self._flux, self.gsparams._gsp) @property def scale_radius(self): return self._r0 diff --git a/galsim/fft.py b/galsim/fft.py index 736238df472..586262ec4e4 100644 --- a/galsim/fft.py +++ b/galsim/fft.py @@ -39,7 +39,7 @@ from . import _galsim from .image import Image, ImageD, ImageCD from .bounds import BoundsI -from .errors import GalSimValueError +from .errors import GalSimValueError, convert_cpp_errors def fft2(a, shift_in=False, shift_out=False): """Compute the 2-dimensional discrete Fourier Transform. @@ -91,7 +91,8 @@ def fft2(a, shift_in=False, shift_out=False): a = a.astype(np.complex128, copy=False) xim = ImageCD(a, xmin = -No2, ymin = -Mo2) kim = ImageCD(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) - _galsim.cfft(xim._image, kim._image, False, shift_in, shift_out) + with convert_cpp_errors(): + _galsim.cfft(xim._image, kim._image, False, shift_in, shift_out) kar = kim.array else: a = a.astype(np.float64, copy=False) @@ -104,7 +105,8 @@ def fft2(a, shift_in=False, shift_out=False): # Faster to start with rfft2 version rkim = ImageCD(BoundsI(0,No2,-Mo2,Mo2-1)) - _galsim.rfft(xim._image, rkim._image, shift_in, shift_out) + with convert_cpp_errors(): + _galsim.rfft(xim._image, rkim._image, shift_in, shift_out) # This only returns kx >= 0. Fill out the full image. kar = np.empty( (M,N), dtype=np.complex128) rkar = rkim.array @@ -179,7 +181,8 @@ def ifft2(a, shift_in=False, shift_out=False): a = a.astype(np.float64, copy=False) kim = ImageD(a, xmin = -No2, ymin = -Mo2) xim = ImageCD(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) - _galsim.cfft(kim._image, xim._image, True, shift_in, shift_out) + with convert_cpp_errors(): + _galsim.cfft(kim._image, xim._image, True, shift_in, shift_out) return xim.array @@ -231,7 +234,8 @@ def rfft2(a, shift_in=False, shift_out=False): a = a.astype(np.float64, copy=False) xim = ImageD(a, xmin = -No2, ymin = -Mo2) kim = ImageCD(BoundsI(0,No2,-Mo2,Mo2-1)) - _galsim.rfft(xim._image, kim._image, shift_in, shift_out) + with convert_cpp_errors(): + _galsim.rfft(xim._image, kim._image, shift_in, shift_out) return kim.array @@ -283,7 +287,8 @@ def irfft2(a, shift_in=False, shift_out=False): a = a.astype(np.complex128, copy=False) kim = ImageCD(a, xmin = 0, ymin = -Mo2) xim = ImageD(BoundsI(-No2,No2+1,-Mo2,Mo2-1)) - _galsim.irfft(kim._image, xim._image, shift_in, shift_out) + with convert_cpp_errors(): + _galsim.irfft(kim._image, xim._image, shift_in, shift_out) xim = xim.subImage(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) return xim.array diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index c40144d8c7e..e6b111bb367 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -29,7 +29,7 @@ from .angle import radians, arcsec, degrees, AngleUnit from . import _galsim from . import fits -from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimWarning, convert_cpp_errors ######################################################################################### # @@ -1289,20 +1289,23 @@ def _parse_tnx_data(self, data): def _apply_pv(self, u, v): # Do this in C++ layer for speed. - _galsim.ApplyPV(len(u), 4, u.ctypes.data, v.ctypes.data, self.pv.ctypes.data) + with convert_cpp_errors(): + _galsim.ApplyPV(len(u), 4, u.ctypes.data, v.ctypes.data, self.pv.ctypes.data) return u, v def _apply_ab(self, x, y): # Do this in C++ layer for speed. dx = x.copy() dy = y.copy() - _galsim.ApplyPV(len(x), len(self.ab[0]), dx.ctypes.data, dy.ctypes.data, - self.ab.ctypes.data) + with convert_cpp_errors(): + _galsim.ApplyPV(len(x), len(self.ab[0]), dx.ctypes.data, dy.ctypes.data, + self.ab.ctypes.data) return x+dx, y+dy def _apply_cd(self, x, y): # Do this in C++ layer for speed. - _galsim.ApplyCD(len(x), x.ctypes.data, y.ctypes.data, self.cd.ctypes.data) + with convert_cpp_errors(): + _galsim.ApplyCD(len(x), x.ctypes.data, y.ctypes.data, self.cd.ctypes.data) return x, y def _uv(self, x, y): @@ -1350,18 +1353,14 @@ def _radec(self, x, y, color=None): def _invert_pv(self, u, v): # Do this in C++ layer for speed. - try: + with convert_cpp_errors(): return _galsim.InvertPV(u, v, self.pv.ctypes.data) - except RuntimeError as err: # pragma: no cover - raise GalSimError(str(err)) def _invert_ab(self, x, y): # Do this in C++ layer for speed. abp_data = 0 if self.abp is None else self.abp.ctypes.data - try: + with convert_cpp_errors(): return _galsim.InvertAB(len(self.ab[0]), x, y, self.ab.ctypes.data, abp_data) - except RuntimeError as err: # pragma: no cover - raise GalSimError(str(err)) def _xy(self, ra, dec, color=None): u, v = self.center.project_rad(ra, dec, projection=self.projection) diff --git a/galsim/fouriersqrt.py b/galsim/fouriersqrt.py index 572bba7b324..1be863eba38 100644 --- a/galsim/fouriersqrt.py +++ b/galsim/fouriersqrt.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject from .utilities import lazy_property, doc_inherit -from .errors import GalSimWarning +from .errors import GalSimWarning, convert_cpp_errors def FourierSqrt(obj, gsparams=None): @@ -101,7 +101,8 @@ def orig_obj(self): return self._orig_obj @property def _sbp(self): - return _galsim.SBFourierSqrt(self.orig_obj._sbp, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBFourierSqrt(self.orig_obj._sbp, self.gsparams._gsp) @property def _noise(self): diff --git a/galsim/gaussian.py b/galsim/gaussian.py index 3335b702b84..4e3357b457d 100644 --- a/galsim/gaussian.py +++ b/galsim/gaussian.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimIncompatibleValuesError +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors class Gaussian(GSObject): @@ -111,7 +111,8 @@ def __init__(self, half_light_radius=None, sigma=None, fwhm=None, flux=1., gspar @lazy_property def _sbp(self): - return _galsim.SBGaussian(self._sigma, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBGaussian(self._sigma, self._flux, self.gsparams._gsp) @property def sigma(self): return self._sigma diff --git a/galsim/gsobject.py b/galsim/gsobject.py index dfccabe6d93..e8139be9c7c 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -59,7 +59,7 @@ from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimWarning +from .errors import GalSimWarning, convert_cpp_errors class GSObject(object): @@ -1836,7 +1836,8 @@ def drawFFT_finish(self, image, kimage, wrap_size, add_to_image): # Perform the fourier transform. breal = _BoundsI(-wrap_size//2, wrap_size//2+1, -wrap_size//2, wrap_size//2-1) real_image = Image(breal, dtype=float) - _galsim.irfft(kimage_wrap._image, real_image._image, True, True) + with convert_cpp_errors(): + _galsim.irfft(kimage_wrap._image, real_image._image, True, True) # Add (a portion of) this to the original image. temp = real_image.subImage(image.bounds) diff --git a/galsim/gsparams.py b/galsim/gsparams.py index 2d083a1e0dc..57be8f3f3ac 100644 --- a/galsim/gsparams.py +++ b/galsim/gsparams.py @@ -20,6 +20,7 @@ """ from . import _galsim +from .errors import convert_cpp_errors class GSParams(object): """GSParams stores a set of numbers that govern how GSObjects make various speed/accuracy @@ -148,7 +149,8 @@ def __init__(self, minimum_fft_size=128, maximum_fft_size=8192, self._small_fraction_of_flux = small_fraction_of_flux # This is the thing that is needed for any c++ calls. - self._gsp = _galsim.GSParams(*self._getinitargs()) + with convert_cpp_errors(): + self._gsp = _galsim.GSParams(*self._getinitargs()) # Make all the attributes read-only @property diff --git a/galsim/hsm.py b/galsim/hsm.py index 9c8aad708ed..b418fb08526 100644 --- a/galsim/hsm.py +++ b/galsim/hsm.py @@ -64,6 +64,7 @@ from .shear import Shear from .image import Image, ImageI, ImageF, ImageD from .errors import GalSimError, GalSimValueError, GalSimHSMError, GalSimIncompatibleValuesError +from .errors import convert_cpp_errors class ShapeData(object): @@ -151,14 +152,15 @@ def __init__(self, image_bounds=BoundsI(), moments_status=-1, raise TypeError("image_bounds must be a BoundsI instance") # The others will raise an appropriate TypeError from the call to _galsim.ShapeData. - self._data = _galsim.ShapeData( - image_bounds._b, int(moments_status), observed_shape.e1, observed_shape.e2, - float(moments_sigma), float(moments_amp), moments_centroid._p, float(moments_rho4), - int(moments_n_iter), int(correction_status), float(corrected_e1), float(corrected_e2), - float(corrected_g1), float(corrected_g2), str(meas_type), float(corrected_shape_err), - str(correction_method), float(resolution_factor), float(psf_sigma), - psf_shape.e1, psf_shape.e2, str(error_message)) - + with convert_cpp_errors(GalSimHSMError): + self._data = _galsim.ShapeData( + image_bounds._b, int(moments_status), observed_shape.e1, observed_shape.e2, + float(moments_sigma), float(moments_amp), moments_centroid._p, + float(moments_rho4), int(moments_n_iter), int(correction_status), + float(corrected_e1), float(corrected_e2), float(corrected_g1), float(corrected_g2), + str(meas_type), float(corrected_shape_err), str(correction_method), + float(resolution_factor), float(psf_sigma), psf_shape.e1, psf_shape.e2, + str(error_message)) @property def image_bounds(self): return BoundsI(self._data.image_bounds) @@ -367,7 +369,8 @@ def __init__(self, nsig_rg=3.0, nsig_rg2=3.6, max_moment_nsig2=25.0, regauss_too self._make_hsmp() def _make_hsmp(self): - self._hsmp = _galsim.HSMParams(*self._getinitargs()) + with convert_cpp_errors(GalSimHSMError): + self._hsmp = _galsim.HSMParams(*self._getinitargs()) def _getinitargs(self): return (self.nsig_rg, self.nsig_rg2, self.max_moment_nsig2, self.regauss_too_small, diff --git a/galsim/image.py b/galsim/image.py index 72f8c8a1a04..9fee99d2154 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -28,7 +28,7 @@ from .wcs import BaseWCS, PixelScale, JacobianWCS from . import utilities from .errors import GalSimError, GalSimBoundsError, GalSimValueError, GalSimImmutableError -from .errors import GalSimUndefinedBoundsError, GalSimIncompatibleValuesError +from .errors import GalSimUndefinedBoundsError, GalSimIncompatibleValuesError, convert_cpp_errors # Sometimes (on 32-bit systems) there are two numpy.int32 types. This can lead to some confusion # when doing arithmetic with images. So just make sure both of them point to ImageViewI in the @@ -715,7 +715,8 @@ def wrap(self, bounds, hermitian=False): # possibly writing data past the edge of the image. ret = self.subImage(bounds) if not hermitian: - _galsim.wrapImage(self._image, bounds._b, False, False) + with convert_cpp_errors(): + _galsim.wrapImage(self._image, bounds._b, False, False) elif hermitian == 'x': if self.bounds.xmin != 0: raise GalSimIncompatibleValuesError( @@ -725,7 +726,8 @@ def wrap(self, bounds, hermitian=False): raise GalSimIncompatibleValuesError( "hermitian == 'x' requires bounds.xmin == 0", hermitian=hermitian, bounds=bounds) - _galsim.wrapImage(self._image, bounds._b, True, False) + with convert_cpp_errors(): + _galsim.wrapImage(self._image, bounds._b, True, False) elif hermitian == 'y': if self.bounds.ymin != 0: raise GalSimIncompatibleValuesError( @@ -735,7 +737,8 @@ def wrap(self, bounds, hermitian=False): raise GalSimIncompatibleValuesError( "hermitian == 'y' requires bounds.ymin == 0", hermitian=hermitian, bounds=bounds) - _galsim.wrapImage(self._image, bounds._b, False, True) + with convert_cpp_errors(): + _galsim.wrapImage(self._image, bounds._b, False, True) else: raise GalSimValueError("Invalid value for hermitian", hermitian, (False, 'x', 'y')) return ret; @@ -745,7 +748,8 @@ def _wrap(self, bounds, hermx, hermy): without some of the sanity checks that the regular function does. """ ret = self.subImage(bounds) - _galsim.wrapImage(self._image, bounds._b, hermx, hermy) + with convert_cpp_errors(): + _galsim.wrapImage(self._image, bounds._b, hermx, hermy) return ret def bin(self, nx, ny): @@ -898,7 +902,8 @@ def calculate_fft(self): dk = np.pi / (No2 * dx) out = Image(BoundsI(0,No2,-No2,No2-1), dtype=np.complex128, scale=dk) - _galsim.rfft(ximage._image, out._image, True, True) + with convert_cpp_errors(): + _galsim.rfft(ximage._image, out._image, True, True) out *= dx*dx out.setOrigin(0,-No2) return out @@ -953,7 +958,8 @@ def calculate_inverse_fft(self): # For the inverse, we need a bit of extra space for the fft. out_extra = Image(BoundsI(-No2,No2+1,-No2,No2-1), dtype=float, scale=dx) - _galsim.irfft(kimage._image, out_extra._image, True, True) + with convert_cpp_errors(): + _galsim.irfft(kimage._image, out_extra._image, True, True) # Now cut off the bit we don't need. out = out_extra.subImage(BoundsI(-No2,No2-1,-No2,No2-1)) out *= (dk * No2 / np.pi)**2 @@ -968,7 +974,8 @@ def good_fft_size(cls, input_size): going to be performing FFTs on an image, these will tend to be faster at performing the FFT. """ - return _galsim.goodFFTSize(int(input_size)) + with convert_cpp_errors(): + return _galsim.goodFFTSize(int(input_size)) def copyFrom(self, rhs): """Copy the contents of another image @@ -1323,7 +1330,8 @@ def _invertSelf(self): are defined. """ # C++ version skips 0's to 1/0 -> 0 instead of inf. - _galsim.invertImage(self._image) + with convert_cpp_errors(): + _galsim.invertImage(self._image) def replaceNegative(self, replace_value=0): """Replace any negative values currently in the image with 0 (or some other value). diff --git a/galsim/inclined.py b/galsim/inclined.py index d7816587fa3..fdf217c1c6a 100644 --- a/galsim/inclined.py +++ b/galsim/inclined.py @@ -25,7 +25,7 @@ from .exponential import Exponential from .angle import Angle from .position import PositionD -from .errors import GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors class InclinedExponential(GSObject): @@ -145,8 +145,9 @@ def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale @lazy_property def _sbp(self): - return _galsim.SBInclinedExponential(self._inclination.rad, self._r0, - self._h0, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBInclinedExponential(self._inclination.rad, self._r0, + self._h0, self._flux, self.gsparams._gsp) @property def inclination(self): return self._inclination @@ -325,12 +326,14 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc raise GalSimRangeError("half_light_radius must be > 0.", half_light_radius, 0.) self._hlr = float(half_light_radius) if self._trunc == 0. or flux_untruncated: - self._r0 = self._hlr / _galsim.SersicHLR(self._n, 1.) + with convert_cpp_errors(): + self._r0 = self._hlr / _galsim.SersicHLR(self._n, 1.) else: if self._trunc <= math.sqrt(2.) * self._hlr: raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", self._trunc, math.sqrt(2.) * self._hlr) - self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) + with convert_cpp_errors(): + self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) else: raise GalSimIncompatibleValuesError( "Either scale_radius or half_light_radius must be specified", @@ -360,7 +363,8 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc # If flux_untrunctated, then the above picked the right radius, but the flux needs # to be updated. if self._trunc > 0.: - self._flux_fraction = _galsim.SersicIntegratedFlux(self._n, self._trunc/self._r0) + with convert_cpp_errors(): + self._flux_fraction = _galsim.SersicIntegratedFlux(self._n, self._trunc/self._r0) if flux_untruncated: self._flux *= self._flux_fraction self._hlr = 0. # This will be updated by getHalfLightRadius if necessary. @@ -369,8 +373,9 @@ def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, sc @lazy_property def _sbp(self): - return _galsim.SBInclinedSersic(self._n, self._inclination.rad, self._r0, self._h0, - self._flux, self._trunc, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBInclinedSersic(self._n, self._inclination.rad, self._r0, self._h0, + self._flux, self._trunc, self.gsparams._gsp) @property def n(self): return self._n @@ -389,7 +394,8 @@ def scale_h_over_r(self): return self._h0 / self._r0 @property def disk_half_light_radius(self): if self._hlr == 0.: - self._hlr = self._r0 * _galsim.SersicHLR(self._n, self._flux_fraction) + with convert_cpp_errors(): + self._hlr = self._r0 * _galsim.SersicHLR(self._n, self._flux_fraction) return self._hlr def __eq__(self, other): diff --git a/galsim/integ.py b/galsim/integ.py index 1476069c2c5..e25ed34f65d 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -24,7 +24,7 @@ from functools import reduce from . import _galsim -from .errors import GalSimError, GalSimRangeError, GalSimValueError +from .errors import GalSimError, GalSimRangeError, GalSimValueError, convert_cpp_errors def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): """Integrate a 1-dimensional function from min to max. @@ -53,7 +53,8 @@ def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): max = float(max) rel_err = float(rel_err) abs_err = float(abs_err) - success, result = _galsim.PyInt1d(func,min,max,rel_err,abs_err) + with convert_cpp_errors(): + success, result = _galsim.PyInt1d(func,min,max,rel_err,abs_err) if success: return result else: diff --git a/galsim/interpolant.py b/galsim/interpolant.py index 8d24f914634..182f70b9cdf 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -26,7 +26,7 @@ from . import _galsim from .gsparams import GSParams from .utilities import lazy_property -from .errors import GalSimValueError +from .errors import GalSimValueError, convert_cpp_errors class Interpolant(object): """A base class that defines how interpolation should be done. @@ -134,7 +134,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Delta(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Delta(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Delta(%r, %r)"%(self._tol, self._gsparams) @@ -170,7 +171,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Nearest(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Nearest(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Nearest(%r, %r)"%(self._tol, self._gsparams) @@ -207,7 +209,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.SincInterpolant(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SincInterpolant(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.SincInterpolant(%r, %r)"%(self._tol, self._gsparams) @@ -243,7 +246,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Linear(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Linear(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Linear(%r, %r)"%(self._tol, self._gsparams) @@ -277,7 +281,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Cubic(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Cubic(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Cubic(%r, %r)"%(self._tol, self._gsparams) @@ -311,7 +316,8 @@ def __init__(self, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Quintic(self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Quintic(self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Quintic(%r, %r)"%(self._tol, self._gsparams) @@ -355,7 +361,8 @@ def __init__(self, n, conserve_dc=True, tol=1.e-4, gsparams=None): @lazy_property def _i(self): - return _galsim.Lanczos(self._n, self._conserve_dc, self._tol, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.Lanczos(self._n, self._conserve_dc, self._tol, self._gsparams._gsp) def __repr__(self): return "galsim.Lanczos(%r, %r, %r, %r)"%(self._n, self._conserve_dc, self._tol, diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 47022a194f9..c79ecb23709 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -35,7 +35,7 @@ from . import _galsim from . import fits from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError -from .errors import GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimIncompatibleValuesError, GalSimWarning, convert_cpp_errors class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -357,12 +357,13 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization= def _sbp(self): min_scale = self._wcs._minScale() max_scale = self._wcs._maxScale() - self._sbii = _galsim.SBInterpolatedImage( - self._xim._image, self._image.bounds._b, self._pad_image.bounds._b, - self._x_interpolant._i, self._k_interpolant._i, - self._stepk*min_scale, - self._maxk*max_scale, - self.gsparams._gsp) + with convert_cpp_errors(): + self._sbii = _galsim.SBInterpolatedImage( + self._xim._image, self._image.bounds._b, self._pad_image.bounds._b, + self._x_interpolant._i, self._k_interpolant._i, + self._stepk*min_scale, + self._maxk*max_scale, + self.gsparams._gsp) self._sbp = self._sbii # Temporary. Will overwrite this with the return value. @@ -550,7 +551,8 @@ def _getStepK(self, calculate_stepk, _force_stepk): b = self._image.bounds & b im = self._image[b] thresh = (1.-self.gsparams.folding_threshold) * self._image_flux - R = _galsim.CalculateSizeContainingFlux(self._image._image, thresh) + with convert_cpp_errors(): + R = _galsim.CalculateSizeContainingFlux(self._image._image, thresh) else: R = np.max(self._image.array.shape) / 2. - 0.5 return self._getSimpleStepK(R) @@ -936,14 +938,16 @@ def k_interpolant(self): @lazy_property def _sbp(self): stepk_image = self.stepk / self.kimage.scale # usually 1, but could be larger - self._sbiki = _galsim.SBInterpolatedKImage( - self.kimage._image, stepk_image, self.k_interpolant._i, self.gsparams._gsp) + with convert_cpp_errors(): + self._sbiki = _galsim.SBInterpolatedKImage( + self.kimage._image, stepk_image, self.k_interpolant._i, self.gsparams._gsp) scale = self.kimage.scale if scale != 1.: - return _galsim.SBTransform(self._sbiki, 1./scale, 0., 0., 1./scale, - _galsim.PositionD(0.,0.), scale**2, - self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBTransform(self._sbiki, 1./scale, 0., 0., 1./scale, + _galsim.PositionD(0.,0.), scale**2, + self.gsparams._gsp) else: return self._sbiki diff --git a/galsim/kolmogorov.py b/galsim/kolmogorov.py index edf8d8ea370..ffe62c46964 100644 --- a/galsim/kolmogorov.py +++ b/galsim/kolmogorov.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimIncompatibleValuesError +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors class Kolmogorov(GSObject): @@ -196,7 +196,8 @@ def __init__(self, lam_over_r0=None, fwhm=None, half_light_radius=None, lam=None @lazy_property def _sbp(self): - return _galsim.SBKolmogorov(self._lor0, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBKolmogorov(self._lor0, self._flux, self.gsparams._gsp) @property def lam_over_r0(self): return self._lor0 diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index c768a0303a5..f89db7fd1b4 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -31,7 +31,6 @@ from .table import LookupTable from . import utilities from . import integ -from . import _galsim from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning def theoryToObserved(gamma1, gamma2, kappa): diff --git a/galsim/moffat.py b/galsim/moffat.py index ce4e05fbb17..f27dba6f404 100644 --- a/galsim/moffat.py +++ b/galsim/moffat.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors class Moffat(GSObject): @@ -103,7 +103,8 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t if self._trunc > 0. and self._trunc <= math.sqrt(2.) * self._hlr: raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.", self._trunc, math.sqrt(2.) * self._hlr) - self._r0 = _galsim.MoffatCalculateSRFromHLR(self._hlr, self._trunc, self._beta) + with convert_cpp_errors(): + self._r0 = _galsim.MoffatCalculateSRFromHLR(self._hlr, self._trunc, self._beta) self._fwhm = 0. elif fwhm is not None: if scale_radius is not None: @@ -124,7 +125,9 @@ def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, t @lazy_property def _sbp(self): - return _galsim.SBMoffat(self._beta, self._r0, self._trunc, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBMoffat(self._beta, self._r0, self._trunc, self._flux, + self.gsparams._gsp) def getFWHM(self): """Return the FWHM for this Moffat profile. diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index b1f2892ed43..d3452b08a2c 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -71,7 +71,6 @@ from heapq import heappush, heappop import numpy as np -from . import _galsim from .gsobject import GSObject from .gsparams import GSParams from .angle import radians, degrees, arcsec, Angle, AngleUnit diff --git a/galsim/photon_array.py b/galsim/photon_array.py index 3b4949418a8..f33e9f9f6a5 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -26,6 +26,7 @@ from .random import UniformDeviate from .angle import radians, arcsec from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError +from .errors import convert_cpp_errors # Add on more methods in the python layer @@ -263,8 +264,9 @@ def _pa(self): if self.hasAllocatedWavelengths(): assert(self._wave.strides[0] == self._wave.itemsize) _wave = self._wave.ctypes.data - return _galsim.PhotonArray(int(self.size()), _x, _y, _flux, _dxdz, _dydz, _wave, - self._is_corr) + with convert_cpp_errors(): + return _galsim.PhotonArray(int(self.size()), _x, _y, _flux, _dxdz, _dydz, _wave, + self._is_corr) def addTo(self, image): """Add flux of photons to an image by binning into pixels. diff --git a/galsim/random.py b/galsim/random.py index 864969de059..b8c317f0868 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -25,6 +25,7 @@ from . import _galsim from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import convert_cpp_errors class BaseDeviate(object): """Base class for all the various random deviates. @@ -118,9 +119,11 @@ def reset(self, seed=None): if isinstance(seed, BaseDeviate): self._reset(seed) elif isinstance(seed, (str, int)): - self._rng = self._rng_type(_galsim.BaseDeviateImpl(seed), *self._rng_args) + with convert_cpp_errors(): + self._rng = self._rng_type(_galsim.BaseDeviateImpl(seed), *self._rng_args) elif seed is None: - self._rng = self._rng_type(_galsim.BaseDeviateImpl(0), *self._rng_args) + with convert_cpp_errors(): + self._rng = self._rng_type(_galsim.BaseDeviateImpl(0), *self._rng_args) else: raise TypeError("BaseDeviate must be initialized with either an int or another " "BaseDeviate") @@ -129,7 +132,8 @@ def _reset(self, rng): """Equivalent to self.reset(rng), but rng must be a BaseDeviate (not an int), and there is no type checking. """ - self._rng = self._rng_type(rng._rng, *self._rng_args) + with convert_cpp_errors(): + self._rng = self._rng_type(rng._rng, *self._rng_args) def duplicate(self): """Create a duplicate of the current Deviate object. The subsequent series from each copy @@ -157,8 +161,9 @@ def duplicate(self): """ ret = BaseDeviate.__new__(self.__class__) ret.__dict__.update(self.__dict__) - rng = _galsim.BaseDeviateImpl(self.serialize()) - ret._rng = self._rng_type(rng, *ret._rng_args) + with convert_cpp_errors(): + rng = _galsim.BaseDeviateImpl(self.serialize()) + ret._rng = self._rng_type(rng, *ret._rng_args) return ret def __copy__(self): @@ -172,8 +177,9 @@ def __getstate__(self): def __setstate__(self, d): self.__dict__ = d - rng = _galsim.BaseDeviateImpl(d['rng_str']) - self._rng = self._rng_type(rng, *self._rng_args) + with convert_cpp_errors(): + rng = _galsim.BaseDeviateImpl(d['rng_str']) + self._rng = self._rng_type(rng, *self._rng_args) def clearCache(self): """Clear the internal cache of the Deviate, if any. This is currently only relevant for diff --git a/galsim/randwalk.py b/galsim/randwalk.py index 729a9d4f6a4..b6137961cef 100644 --- a/galsim/randwalk.py +++ b/galsim/randwalk.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .position import PositionD from .utilities import lazy_property, doc_inherit -from .errors import GalSimRangeError +from .errors import GalSimRangeError, convert_cpp_errors class RandomWalk(GSObject): """ @@ -125,9 +125,10 @@ def deltas(self): fluxper=self._flux/self._npoints for p in self._points: - d = _galsim.SBDeltaFunction(fluxper, self.gsparams._gsp) - d = _galsim.SBTransform(d, 1.0, 0.0, 0.0, 1.0, _galsim.PositionD(p[0],p[1]), 1.0, - self.gsparams._gsp) + with convert_cpp_errors(): + d = _galsim.SBDeltaFunction(fluxper, self.gsparams._gsp) + d = _galsim.SBTransform(d, 1.0, 0.0, 0.0, 1.0, _galsim.PositionD(p[0],p[1]), 1.0, + self.gsparams._gsp) deltas.append(d) return deltas @@ -136,7 +137,8 @@ def deltas(self): @lazy_property def _sbp(self): - return _galsim.SBAdd(self.deltas, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAdd(self.deltas, self.gsparams._gsp) @property def input_half_light_radius(self): diff --git a/galsim/real.py b/galsim/real.py index 20eeb72860f..654e054e165 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -39,9 +39,14 @@ from .gsparams import GSParams from .chromatic import ChromaticSum from .position import PositionD -from .utilities import doc_inherit +from .utilities import doc_inherit, convert_interpolant +from .interpolant import Quintic +from .interpolatedimage import InterpolatedImage, _InterpolatedKImage +from .image import ImageCD +from .correlatednoise import CovarianceSpectrum +from . import _galsim from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimIndexError +from .errors import GalSimIndexError, convert_cpp_errors HST_area = 45238.93416 # Area of HST primary mirror in cm^2 from Synphot User's Guide. @@ -1147,12 +1152,6 @@ def makeFromImages(cls, images, bands, PSFs, xis, **kwargs): def _initialize(self, imgs, bands, xis, PSFs, SEDs=None, k_interpolant=None, maxk=None, pad_factor=4., area_norm=1.0, noise_pad_size=0, gsparams=None): - from .interpolant import Quintic - from .interpolatedimage import InterpolatedImage, _InterpolatedKImage - from .image import ImageCD - from . import utilities - from . import _galsim - from .correlatednoise import CovarianceSpectrum if SEDs is None: SEDs = self._poly_SEDs(bands) @@ -1161,7 +1160,7 @@ def _initialize(self, imgs, bands, xis, PSFs, if k_interpolant is None: k_interpolant = Quintic(tol=1e-4) else: - k_interpolant = utilities.convert_interpolant(k_interpolant) + k_interpolant = convert_interpolant(k_interpolant) self._area_norm = area_norm self._k_interpolant = k_interpolant @@ -1253,10 +1252,11 @@ def _initialize(self, imgs, bands, xis, PSFs, # Solve the weighted linear least squares problem for each Fourier mode. This is # effectively a constrained chromatic deconvolution. Take advantage of symmetries. - _galsim.ComputeCRGCoefficients( - coef.ctypes.data, Sigma.ctypes.data, - w.ctypes.data, kimgs.ctypes.data, PSF_eff_kimgs.ctypes.data, - NSED, Nim, nk, nk) + with convert_cpp_errors(): + _galsim.ComputeCRGCoefficients( + coef.ctypes.data, Sigma.ctypes.data, + w.ctypes.data, kimgs.ctypes.data, PSF_eff_kimgs.ctypes.data, + NSED, Nim, nk, nk) # Reorder these so they correspond to (NSED, nky, nkx) and (NSED, NSED, nky, nkx) shapes. coef = np.transpose(coef, (2,0,1)) diff --git a/galsim/second_kick.py b/galsim/second_kick.py index ef8e1293ad0..642a69725c2 100644 --- a/galsim/second_kick.py +++ b/galsim/second_kick.py @@ -34,6 +34,7 @@ from .position import PositionD from .angle import arcsec, AngleUnit, radians from .deltafunction import DeltaFunction +from .errors import convert_cpp_errors class SecondKick(GSObject): """Class describing the expectation value of the high-k turbulence portion of an atmospheric PSF @@ -118,21 +119,25 @@ def __init__(self, lam, r0, diam, obscuration=0, kcrit=0.2, flux=1, @lazy_property def _sbs(self): lam_over_r0 = (1.e-9*self._lam/self._r0)*self._scale - return _galsim.SBSecondKick(lam_over_r0, self._kcrit, self._flux, self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBSecondKick(lam_over_r0, self._kcrit, self._flux, self._gsparams._gsp) @lazy_property def _sba(self): lam_over_diam = (1.e-9*self._lam/self._diam)*self._scale - return _galsim.SBAiry(lam_over_diam, self._obscuration, 1., self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAiry(lam_over_diam, self._obscuration, 1., self._gsparams._gsp) @lazy_property def _sbd(self): - return _galsim.SBDeltaFunction(self._sbs.getDelta(), self._gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBDeltaFunction(self._sbs.getDelta(), self._gsparams._gsp) @lazy_property def _sbp(self): - full_sbs = _galsim.SBAdd([self._sbs, self._sbd], self._gsparams._gsp) - return _galsim.SBConvolve([full_sbs, self._sba], False, self._gsparams._gsp) + with convert_cpp_errors(): + full_sbs = _galsim.SBAdd([self._sbs, self._sbd], self._gsparams._gsp) + return _galsim.SBConvolve([full_sbs, self._sba], False, self._gsparams._gsp) @property def flux(self): diff --git a/galsim/sensor.py b/galsim/sensor.py index ce84e24ee15..73d29c7ca28 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -36,7 +36,7 @@ from .table import LookupTable from .random import UniformDeviate from . import meta_data -from .errors import GalSimUndefinedBoundsError +from .errors import GalSimUndefinedBoundsError, convert_cpp_errors class Sensor(object): """ @@ -209,11 +209,12 @@ def _init_silicon(self): raise OSError("Vertex file %s does not match config file %s"%( self.vertex_file, self.config_file)) - self._silicon = _galsim.Silicon(NumVertices, num_elec, Nx, Ny, self.qdist, nrecalc, - diff_step, PixelSize, SensorThickness, - vertex_data.ctypes.data, - self.treering_func._tab, self.treering_center._p, - self.abs_length_table._tab, self.transpose) + with convert_cpp_errors(): + self._silicon = _galsim.Silicon(NumVertices, num_elec, Nx, Ny, self.qdist, nrecalc, + diff_step, PixelSize, SensorThickness, + vertex_data.ctypes.data, + self.treering_func._tab, self.treering_center._p, + self.abs_length_table._tab, self.transpose) def __str__(self): s = 'galsim.SiliconSensor(%r'%self.name diff --git a/galsim/sersic.py b/galsim/sersic.py index dd06194ae78..00e22b74068 100644 --- a/galsim/sersic.py +++ b/galsim/sersic.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors class Sersic(GSObject): """A class describing a Sersic profile. @@ -233,7 +233,8 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, if self._trunc <= math.sqrt(2.) * self._hlr: raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", self._trunc, math.sqrt(2.) * self._hlr) - self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) + with convert_cpp_errors(): + self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) elif scale_radius is not None: self._r0 = float(scale_radius) self._hlr = 0. @@ -253,16 +254,19 @@ def __init__(self, n, half_light_radius=None, scale_radius=None, def calculateIntegratedFlux(self, r): """Return the fraction of the total flux enclosed within a given radius, r""" - return _galsim.SersicIntegratedFlux(self._n, float(r)/self._r0) + with convert_cpp_errors(): + return _galsim.SersicIntegratedFlux(self._n, float(r)/self._r0) def calculateHLRFactor(self): """Calculate the half-light-radius in units of the scale radius. """ - return _galsim.SersicHLR(self._n, self._flux_fraction) + with convert_cpp_errors(): + return _galsim.SersicHLR(self._n, self._flux_fraction) @lazy_property def _sbp(self): - return _galsim.SBSersic(self._n, self._r0, self._flux, self._trunc, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBSersic(self._n, self._r0, self._flux, self._trunc, self.gsparams._gsp) @property def n(self): return self._n diff --git a/galsim/shapelet.py b/galsim/shapelet.py index b52381335ad..692ecae2b20 100644 --- a/galsim/shapelet.py +++ b/galsim/shapelet.py @@ -28,7 +28,7 @@ from .image import Image from .utilities import doc_inherit from . import _galsim -from .errors import GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimValueError, GalSimIncompatibleValuesError, convert_cpp_errors class Shapelet(GSObject): @@ -140,8 +140,9 @@ def __init__(self, sigma, order, bvec=None, gsparams=None): "bvec is the wrong size for the provided order", bvec=bvec, order=order) self._bvec = np.ascontiguousarray(bvec, dtype=float) - self._sbp = _galsim.SBShapelet(self._sigma, self._order, self._bvec.ctypes.data, - self.gsparams._gsp) + with convert_cpp_errors(): + self._sbp = _galsim.SBShapelet(self._sigma, self._order, self._bvec.ctypes.data, + self.gsparams._gsp) @classmethod def size(cls, order): @@ -190,8 +191,9 @@ def __getstate__(self): def __setstate__(self, d): self.__dict__ = d - self._sbp = _galsim.SBShapelet(self._sigma, self._order, self._bvec.ctypes.data, - self.gsparams._gsp) + with convert_cpp_errors(): + self._sbp = _galsim.SBShapelet(self._sigma, self._order, self._bvec.ctypes.data, + self.gsparams._gsp) @property def _maxk(self): @@ -288,14 +290,16 @@ def fit(cls, sigma, order, image, center=None, normalization='flux', gsparams=No # Make it double precision if it is not. image = Image(image, dtype=np.float64, copy=False) - _galsim.ShapeletFitImage(ret._sigma, ret._order, ret._bvec.ctypes.data, - image._image, image.scale, center._p) + with convert_cpp_errors(): + _galsim.ShapeletFitImage(ret._sigma, ret._order, ret._bvec.ctypes.data, + image._image, image.scale, center._p) if normalization.lower() == "flux" or normalization.lower() == "f": ret._bvec /= image.scale**2 # Update the SBProfile, since it doesn't have the right bvector anymore. - ret._sbp = _galsim.SBShapelet(ret._sigma, ret._order, ret._bvec.ctypes.data, - ret.gsparams._gsp) + with convert_cpp_errors(): + ret._sbp = _galsim.SBShapelet(ret._sigma, ret._order, ret._bvec.ctypes.data, + ret.gsparams._gsp) return ret diff --git a/galsim/spergel.py b/galsim/spergel.py index 720f7ac830a..c29ef03715b 100644 --- a/galsim/spergel.py +++ b/galsim/spergel.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors class Spergel(GSObject): @@ -131,7 +131,8 @@ def __init__(self, nu, half_light_radius=None, scale_radius=None, "Only one of scale_radius or half_light_radius may be specified", half_light_radius=half_light_radius, scale_radius=scale_radius) self._hlr = float(half_light_radius) - self._r0 = self._hlr / _galsim.SpergelCalculateHLR(self._nu) + with convert_cpp_errors(): + self._r0 = self._hlr / _galsim.SpergelCalculateHLR(self._nu) elif scale_radius is not None: self._r0 = float(scale_radius) self._hlr = 0. @@ -142,7 +143,8 @@ def __init__(self, nu, half_light_radius=None, scale_radius=None, @lazy_property def _sbp(self): - return _galsim.SBSpergel(self._nu, self._r0, self._flux, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBSpergel(self._nu, self._r0, self._flux, self.gsparams._gsp) @property def nu(self): return self._nu @@ -151,7 +153,8 @@ def scale_radius(self): return self._r0 @property def half_light_radius(self): if self._hlr == 0.: - self._hlr = self._r0 * _galsim.SpergelCalculateHLR(self._nu) + with convert_cpp_errors(): + self._hlr = self._r0 * _galsim.SpergelCalculateHLR(self._nu) return self._hlr def calculateIntegratedFlux(self, r): diff --git a/galsim/sum.py b/galsim/sum.py index a3d49ca46ae..2301de1c0be 100644 --- a/galsim/sum.py +++ b/galsim/sum.py @@ -22,6 +22,8 @@ from .gsobject import GSObject from .chromatic import ChromaticObject, ChromaticSum from .utilities import lazy_property, doc_inherit +from . import _galsim +from .errors import convert_cpp_errors def Add(*args, **kwargs): """A function for adding 2 or more GSObject or ChromaticObject instances. @@ -149,10 +151,10 @@ def obj_list(self): return self._obj_list @property def _sbp(self): - from . import _galsim # NB. I only need this until compound and transform are reimplemented in Python... sb_list = [obj._sbp for obj in self.obj_list] - return _galsim.SBAdd(sb_list, self.gsparams._gsp) + with convert_cpp_errors(): + return _galsim.SBAdd(sb_list, self.gsparams._gsp) @lazy_property def _flux(self): diff --git a/galsim/table.py b/galsim/table.py index 6fbab3ca207..bd8344392aa 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -29,7 +29,7 @@ from .position import PositionD from .bounds import BoundsD from .errors import GalSimRangeError, GalSimBoundsError, GalSimValueError -from .errors import GalSimIncompatibleValuesError +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors class LookupTable(object): """ @@ -134,8 +134,9 @@ def _tab(self): if self.x_log: self._x = np.log(self._x) if self.f_log: self._f = np.log(self._f) - return _galsim._LookupTable(self._x.ctypes.data, self._f.ctypes.data, - len(self._x), self.interpolant) + with convert_cpp_errors(): + return _galsim._LookupTable(self._x.ctypes.data, self._f.ctypes.data, + len(self._x), self.interpolant) @property def x_min(self): return self._x_min @@ -462,9 +463,10 @@ def __init__(self, x, y, f, interpolant='linear', edge_mode='raise', constant=0) @lazy_property def _tab(self): - return _galsim._LookupTable2D(self.x.ctypes.data, self.y.ctypes.data, - self.f.ctypes.data, len(self.x), len(self.y), - self.interpolant) + with convert_cpp_errors(): + return _galsim._LookupTable2D(self.x.ctypes.data, self.y.ctypes.data, + self.f.ctypes.data, len(self.x), len(self.y), + self.interpolant) def getXArgs(self): return self.x diff --git a/galsim/transform.py b/galsim/transform.py index b2cdfa46de4..11fcbc151d5 100644 --- a/galsim/transform.py +++ b/galsim/transform.py @@ -28,7 +28,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit, WeakMethod from .position import PositionD -from .errors import GalSimError +from .errors import GalSimError, convert_cpp_errors def Transform(obj, jac=(1.,0.,0.,1.), offset=PositionD(0.,0.), flux_ratio=1., gsparams=None): """A function for transforming either a GSObject or ChromaticObject. @@ -163,11 +163,9 @@ def _flux(self): @lazy_property def _sbp(self): dudx, dudy, dvdx, dvdy = self._jac.ravel() - try: + with convert_cpp_errors(): return _galsim.SBTransform(self._original._sbp, dudx, dudy, dvdx, dvdy, self._offset._p, self._flux_ratio, self.gsparams._gsp) - except RuntimeError as e: - raise GalSimError(str(e)) @lazy_property def _noise(self): diff --git a/galsim/vonkarman.py b/galsim/vonkarman.py index 1ad9b29f12e..66d72c1c736 100644 --- a/galsim/vonkarman.py +++ b/galsim/vonkarman.py @@ -27,7 +27,7 @@ from .utilities import lazy_property, doc_inherit from .position import PositionD from .angle import arcsec, AngleUnit -from .errors import GalSimError, GalSimWarning +from .errors import GalSimError, GalSimWarning, convert_cpp_errors class VonKarman(GSObject): @@ -117,13 +117,9 @@ def __init__(self, lam, r0, L0=25.0, flux=1, scale_unit=arcsec, @lazy_property def _sbvk(self): - try: + with convert_cpp_errors(): sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux, self._scale, self._do_delta, self._gsparams._gsp) - except RuntimeError as err: # pragma: no cover - # There are a couple possible failure modes that can be found in the - # C++ layer. Turn them into GalSimErrors. - raise GalSimError(std(err)) self._delta = sbvk.getDelta() if not self._suppress: @@ -133,9 +129,10 @@ def _sbvk(self): "Please see docstring for information about this component and how " "to toggle it.", GalSimWarning) if self._do_delta: - sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, - self._flux-self._delta, self._scale, - self._do_delta, self._gsparams._gsp) + with convert_cpp_errors(): + sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, + self._flux-self._delta, self._scale, + self._do_delta, self._gsparams._gsp) return sbvk @lazy_property @@ -143,8 +140,9 @@ def _sbp(self): # Add in a delta function with appropriate amplitude if requested. if self._do_delta: sbvk = self._sbvk - sbdelta = _galsim.SBDeltaFunction(self._delta, self._gsparams._gsp) - return _galsim.SBAdd([sbvk, sbdelta], self._gsparams._gsp) + with convert_cpp_errors(): + sbdelta = _galsim.SBDeltaFunction(self._delta, self._gsparams._gsp) + return _galsim.SBAdd([sbvk, sbdelta], self._gsparams._gsp) else: return self._sbvk From cbcca5b40b4acce5c024dddf2809aca3a30873b9 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 30 Apr 2018 10:13:46 -0400 Subject: [PATCH 57/96] Increase test coverage of wcs (#755) --- CHANGELOG.md | 10 +- galsim/fitswcs.py | 315 ++++++++++++++++++++++++-------------------- galsim/wcs.py | 140 ++++++++------------ tests/test_wcs.py | 327 ++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 538 insertions(+), 254 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52be8bbcd2d..307e0d4d094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,18 +8,20 @@ either pip or setup.py. Dependency Changes ------------------ +- Officially no longer support Python 2.6. (#755) - No longer support pre-astropy versions of pyfits (now bundled in astropy - as astropy.io.fits). (#755) + as astropy.io.fits). Nor astropy versions <1.0. (#755) - Added LSSTDESC.Coord, which contains the functionality that used to be in GalSim as the Angle and CelestialCoord classes. We moved it to a separate repo so people could more easily use this functionality without requiring all of GalSim as a dependency. (#809b) - Removed dependency on boost. (#809) - Removed dependency on TMV. (#809) -- Added dependency on pybind11. (#809) -- Added dependency on Eigen. (#809) +- Added dependency on pybind11. (You can still use boost if you want using + the SCons installation method.) (#809) +- Added dependency on Eigen. (You can still use TMV if you want using the + SCons installation method.) (#809) - FFTW is now the only dependency that pip cannot handle automatically. (#809) -- Officially no longer support Python 2.6. (Pretty sure no one cares.) API Changes diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index e6b111bb367..54feb594394 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -24,12 +24,13 @@ import warnings import numpy as np -from .wcs import CelestialWCS, JacobianWCS, AffineTransform +from .wcs import CelestialWCS from .position import PositionD, PositionI from .angle import radians, arcsec, degrees, AngleUnit from . import _galsim from . import fits -from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimWarning, convert_cpp_errors +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimWarning, convert_cpp_errors ######################################################################################### # @@ -132,34 +133,37 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= raise GalSimIncompatibleValuesError( "Cannot provide both file_name and wcs", file_name=file_name, wcs=wcs) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) - header = hdu.header - - # At least as late as version 1.1.2, astropy thinks it knows how to parse ZPX files, - # but can at least sometimes seg fault when it tries to parse the header. Check for - # that explicitly here and raise an exception before getting to _load_from_header - # I think this is fixed in 1.2, but I'm not 100% sure. - # Update: Nope. Still broken. cf. Issue #783. - # TODO: If they ever fix this bug, use the correct version here. - if (astropy.__version__ < '999' and header is not None and - 'CTYPE1' in header and 'ZPX' in header['CTYPE1'].upper()): - raise GalSimError("AstropyWCS cannot (always) parse ZPX files") - - # Load the wcs from the header. - if header is not None: - if self._tag is None: - self.header = header - if wcs is not None: - raise GalSimIncompatibleValuesError( - "Cannot provide both pyfits header and wcs", header=header, wcs=wcs) - wcs = self._load_from_header(header, hdu) - if wcs is None: - raise GalSimIncompatibleValuesError( - "Must provide one of file_name, header, or wcs", - file_name=file_name, header=header, wcs=wcs) + try: + if file_name is not None: + header = hdu.header + + # At least as late as version 2.0.4, astropy thinks it knows how to parse ZPX files, + # but can at least sometimes seg fault when it tries to parse the header. Check for + # that explicitly here and raise an exception before getting to _load_from_header + # TODO: If they ever fix this bug, use the correct version here. + if (astropy.__version__ < '999' and header is not None and + 'CTYPE1' in header and 'ZPX' in header['CTYPE1'].upper()): + raise GalSimError("AstropyWCS cannot (always) parse ZPX files") + + # Load the wcs from the header. + if header is not None: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcs", header=header, wcs=wcs) + self.header = fits.FitsHeader(header) + wcs = self._load_from_header(self.header) + else: + self.header = None - if file_name is not None: - fits.closeHDUList(hdu_list, fin) + if wcs is None: + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcs", + file_name=file_name, header=header, wcs=wcs) + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) # If astropy.wcs cannot parse the header, it won't notice from just doing the # WCS(header) command. It will silently move on, thinking things are fine until @@ -169,12 +173,17 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= with warnings.catch_warnings(): warnings.simplefilter("ignore") ra, dec = wcs.all_pix2world( [ [0, 0] ], 1)[0] - except Exception as err: # pragma: no cover - raise GalSimError("AstropyWCS was unable to read the WCS specification in the header.") + except KeyboardInterrupt: + raise + except Exception as err: + raise OSError("AstropyWCS was unable to read the WCS specification in the header." + "Caught error: %r"%err) + if not wcs.is_celestial: + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) self._wcs = wcs - def _load_from_header(self, header, hdu): + def _load_from_header(self, header): import astropy.wcs from . import fits self._fix_header(header) @@ -228,11 +237,11 @@ def _radec(self, x, y, color=None): # Old versions fail with an AttributeError about astropy.wcs.Wcsprm.lattype # cf. https://github.com/astropy/astropy/pull/1463 # This has been fixed for a while now, but leave in this workaround for old versions. - ra, dec = self._wcs.all_pix2world(x1, y1, 1, ra_dec_order=True) + ra, dec = self.wcs.all_pix2world(x1, y1, 1, ra_dec_order=True) except AttributeError: # pragma: no cover # If that failed, then we should be on version < 1.0.1, and the header should have # been fixed above by _fix_header. So this should work correctly. - ra, dec = self._wcs.all_pix2world(x1, y1, 1) + ra, dec = self.wcs.all_pix2world(x1, y1, 1) # astropy outputs ra, dec in degrees. Need to convert to radians. factor = degrees / radians @@ -263,7 +272,7 @@ def _xy(self, ra, dec, color=None): with warnings.catch_warnings(): warnings.simplefilter("ignore") - xy = self._wcs.all_world2pix(rd, 1, ra_dec_order=True)[0] + xy = self.wcs.all_world2pix(rd, 1, ra_dec_order=True)[0] else: # pragma: no cover # This section is basically a copy of astropy.wcs's _all_world2pix function, but # simplified a bit to remove some features we don't need, and with corrections @@ -280,13 +289,13 @@ def _xy(self, ra, dec, color=None): # It seems to be harmless, so we explicitly ignore it here: with warnings.catch_warnings(): warnings.simplefilter("ignore") - xy0 = self._wcs.wcs_world2pix(rd, origin) + xy0 = self.wcs.wcs_world2pix(rd, origin) # Note that the fmod bit accounts for the possibility that ra and the ra returned # from all_pix2world have a different wrapping around 360. We fmod dec too even # though it won't do anything, since that's how the numpy array fmod2 has to work. func = lambda pix: ( - (np.fmod(self._wcs.all_pix2world(np.atleast_2d(pix),origin) - + (np.fmod(self.wcs.all_pix2world(np.atleast_2d(pix),origin) - rd + 180,360) - 180).ravel() ) # This is the main bit that the astropy function is missing. @@ -302,7 +311,7 @@ def _xy(self, ra, dec, color=None): # starting at exactly the right value, it is hugely more efficient to give it an # estimate of alpha, since it is not typically near unity in this case, so it is much # faster to start with something closer to the right value. - alpha = np.mean(np.abs(self._wcs.wcs.get_cdelt())) + alpha = np.mean(np.abs(self.wcs.wcs.get_cdelt())) with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -320,55 +329,51 @@ def _writeHeader(self, header, bounds): # Make a new header with the contents of this WCS. # Note: relax = True means to write out non-standard FITS types. # Weirdly, this is the default when reading the header, but not when writing. - header.update(self._wcs.to_header(relax=True)) + header.update(self.wcs.to_header(relax=True)) # And write the name as a special GalSim key header["GS_WCS"] = ("AstropyWCS", "GalSim WCS name") - # Finally, update the CRPIX items if necessary. - if self.origin.x != 0: - header["CRPIX1"] = header["CRPIX1"] + self.origin.x - if self.origin.y != 0: - header["CRPIX2"] = header["CRPIX2"] + self.origin.y + # And the image origin. + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") return header @staticmethod def _readHeader(header): - return AstropyWCS(header=header) + x0 = header.get("GS_X0",0.) + y0 = header.get("GS_Y0",0.) + return AstropyWCS(header=header, origin=PositionD(x0,y0)) def copy(self): - # The copy module version of copying the dict works fine here. - import copy - return copy.copy(self) + ret = AstropyWCS.__new__(AstropyWCS) + ret.__dict__.update(self.__dict__) + return ret def __eq__(self, other): return ( isinstance(other, AstropyWCS) and - self._wcs.to_header(relax=True) == other.wcs.to_header(relax=True) and + self.wcs.to_header(relax=True) == other.wcs.to_header(relax=True) and self.origin == other.origin ) def __repr__(self): - if self._tag is None: - if hasattr(self,'header'): - self._tag = 'header=%r'%fits.FitsHeader(self.header) - else: - self._tag = 'wcs=%r'%self.wcs - return "galsim.AstropyWCS(%s, origin=%r)"%(self._tag, self.origin) + if self._tag is not None: + tag = self._tag + elif self.header is not None: + tag = 'header=%r'%self.header + else: + tag = 'wcs=%r'%self.wcs + return "galsim.AstropyWCS(%s, origin=%r)"%(tag, self.origin) def __hash__(self): return hash(repr(self)) def __getstate__(self): d = self.__dict__.copy() - # If header or wcs is in the tag, then it might still be picklable, so let pickle - # try and raise the normal exception if it can't. - if self._tag is not None and 'wcs' not in self._tag and 'header' not in self._tag: # pragma: no branch - del d['_wcs'] + del d['_wcs'] return d def __setstate__(self, d): import galsim self.__dict__ = d - hdu, hdu_list, fin = eval('galsim.fits.readFile('+d['_tag']+')') - self._wcs = self._load_from_header(hdu.header, hdu) - fits.closeHDUList(hdu_list, fin) + self._wcs = self._load_from_header(self.header) class PyAstWCS(CelestialWCS): @@ -408,7 +413,7 @@ class PyAstWCS(CelestialWCS): a FitsHeader object. [default: None] @param compression Which decompression scheme to use (if any). See galsim.fits.read() for the available options. [default:'auto'] - @param wcsinfo An existing starlink.Ast.WcsMap [default: None] + @param wcsinfo An existing starlink.Ast.FrameSet [default: None] @param origin Optional origin position for the image coordinate system. If provided, it should be a PositionD or PositionI. [default: None] @@ -445,43 +450,52 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= "Cannot provide both file_name and wcsinfo", file_name=file_name, wcsinfo=wcsinfo) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) - header = hdu.header - # Load the wcs from the header. - if header is not None: - if self._tag is None: - self.header = header - if wcsinfo is not None: - raise GalSimIncompatibleValuesError( - "Cannot provide both pyfits header and wcsinfo", header=header, wcsinfo=wcsinfo) - wcsinfo = self._load_from_header(header, hdu) + try: + if file_name is not None: + header = hdu.header - if wcsinfo is None: - raise GalSimIncompatibleValuesError( - "Must provide one of file_name, header, or wcsinfo", - file_name=file_name, header=header, wcsinfo=wcsinfo) + # Load the wcs from the header. + if header is not None: + if wcsinfo is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcsinfo", + header=header, wcsinfo=wcsinfo) + self.header = fits.FitsHeader(header) + wcsinfo = self._load_from_header(self.header) + else: + self.header = None - # We can only handle WCS with 2 pixel axes (given by Nin) and 2 WCS axes - # (given by Nout). - if wcsinfo.Nin != 2 or wcsinfo.Nout != 2: - raise GalSimError("The world coordinate system is not 2-dimensional") + if wcsinfo is None: + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcsinfo", + file_name=file_name, header=header, wcsinfo=wcsinfo) - if file_name is not None: - fits.closeHDUList(hdu_list, fin) + # We can only handle WCS with 2 pixel axes (given by Nin) and 2 WCS axes + # (given by Nout). + if wcsinfo.Nin != 2 or wcsinfo.Nout != 2: # pragma: no cover + raise GalSimError("The world coordinate system is not 2-dimensional") + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) self._wcsinfo = wcsinfo - def _load_from_header(self, header, hdu): + def _load_from_header(self, header): import starlink.Atl # Note: For much of this class implementation, I've followed the example provided here: # http://dsberry.github.io/starlink/node4.html self._fix_header(header) - # PyFITSAdapter requires an hdu, not a header, so if we were given a header directly, - # then we need to mock it up. - if hdu is None: - from ._pyfits import pyfits - hdu = pyfits.PrimaryHDU() - fits.FitsHeader(hdu_list=hdu).update(header) + + # PyFITSAdapter requires an hdu, not a header, so just put it in a pyfits header object. + # It turns out there are subtle differences between this and using the original FITS + # file hdu that we read in above. So there is a slight inefficiency here in creating + # a new blank PrimaryHDU for this. But in return we gain more reliable serializability. + from ._pyfits import pyfits + hdu = pyfits.PrimaryHDU() + fits.FitsHeader(hdu_list=hdu).update(header) + with warnings.catch_warnings(): warnings.simplefilter("ignore") # They aren't so good at keeping up with the latest pyfits and numpy syntax, so @@ -492,7 +506,7 @@ def _load_from_header(self, header, hdu): wcsinfo = fc.read() if wcsinfo is None: - raise GalSimError("Failed to read WCS information from fits file") + raise OSError("Failed to read WCS information from fits file") # The PyAst WCS might not have (RA,Dec) axes, which we want. It might for instance have # (Dec, RA) instead. If it's possible to convert to an (RA,Dec) system, this next line @@ -529,7 +543,7 @@ def _radec(self, x, y, color=None): # if input is either scalar x,y or two arrays. xy = np.array([np.atleast_1d(x), np.atleast_1d(y)]) - ra, dec = self._wcsinfo.tran( xy ) + ra, dec = self.wcsinfo.tran( xy ) # PyAst returns ra, dec in radians, so we're good. try: @@ -544,7 +558,7 @@ def _radec(self, x, y, color=None): def _xy(self, ra, dec, color=None): rd = np.array([np.atleast_1d(ra), np.atleast_1d(dec)]) - x, y = self._wcsinfo.tran( rd, False ) + x, y = self.wcsinfo.tran( rd, False ) return x[0], y[0] def _newOrigin(self, origin): @@ -566,23 +580,23 @@ def _writeHeader(self, header, bounds): fc = starlink.Ast.FitsChan(None, starlink.Atl.PyFITSAdapter(hdu) , "Encoding=FITS-WCS") # Let Ast know how big the image is that we'll be writing. for key in ['NAXIS', 'NAXIS1', 'NAXIS2']: - if key in header: + if key in header: # pragma: no branch fc[key] = header[key] - success = fc.write(self._wcsinfo) + success = fc.write(self.wcsinfo) # PyAst doesn't write out TPV or ZPX correctly. It writes them as TAN and ZPN # respectively. However, if the maximum error is less than 0.1 pixel, it claims # success nonetheless. This doesn't seem accurate enough for many purposes, # so we need to countermand that. # The easiest way I found to check for them is that the string TPN is in the string # version of wcsinfo. So check for that and set success = False in that case. - if 'TPN' in str(self._wcsinfo): success = False + if 'TPN' in str(self.wcsinfo): success = False # Likewise for SIP. MPF seems to be an appropriate string to look for. - if 'MPF' in str(self._wcsinfo): success = False + if 'MPF' in str(self.wcsinfo): success = False if not success: # This should always work, since it uses starlinks own proprietary encoding, but # it won't necessarily be readable by ds9. fc = starlink.Ast.FitsChan(None, starlink.Atl.PyFITSAdapter(hdu)) - fc.write(self._wcsinfo) + fc.write(self.wcsinfo) fc.writefits() header.update(hdu.header) @@ -600,40 +614,36 @@ def _readHeader(header): return PyAstWCS(header=header, origin=PositionD(x0,y0)) def copy(self): - # The copy module version of copying the dict works fine here. - import copy - return copy.copy(self) + ret = PyAstWCS.__new__(PyAstWCS) + ret.__dict__.update(self.__dict__) + return ret def __eq__(self, other): return ( isinstance(other, PyAstWCS) and - str(self._wcsinfo) == str(other.wcsinfo) and + repr(self.wcsinfo) == repr(other.wcsinfo) and self.origin == other.origin) def __repr__(self): - if self._tag is None: - if hasattr(self, 'header'): - self._tag = 'header=%r'%fits.FitsHeader(self.header) - else: - # Ast doesn't have a good repr for a FrameSet, so do it ourselves. - self._tag = 'wcsinfo='%id(self.wcsinfo) - return "galsim.PyAstWCS(%s, origin=%r)"%(self._tag, self.origin) + if self._tag is not None: + tag = self._tag + elif self.header is not None: + tag = 'header=%r'%self.header + else: + # Ast doesn't have a good repr for a FrameSet, so do it ourselves. + tag = 'wcsinfo='%id(self.wcsinfo) + return "galsim.PyAstWCS(%s, origin=%r)"%(tag, self.origin) def __hash__(self): return hash(repr(self)) def __getstate__(self): d = self.__dict__.copy() - # If header or wcsinfo is in the tag, then we can't pickle. Just leave it alone - # and let pickle raise the normal exception. - if self._tag is not None and 'wcsinfo' not in self._tag and 'header' not in self._tag: # pragma: no branch - del d['_wcsinfo'] + del d['_wcsinfo'] return d def __setstate__(self, d): import galsim self.__dict__ = d - hdu, hdu_list, fin = eval('galsim.fits.readFile('+d['_tag']+')') - self._wcsinfo = self._load_from_header(hdu.header, hdu) - fits.closeHDUList(hdu_list, fin) + self._wcsinfo = self._load_from_header(self.header) # I can't figure out how to get wcstools installed in the travis environment (cf. .travis.yml). @@ -687,6 +697,12 @@ def __init__(self, file_name, dir=None, origin=None): if len(results) == 0: raise OSError('wcstools (specifically xy2sky) was unable to read '+file_name) + # wcstools supports LINEAR WCS's, but we don't want to allow them, since then + # the CelestialWCS base class is inappropriate. The clue to detect this is that + # the results only have 4 values, rather than use usual 5 (missing epoch). + if len(results.split()) == 4: + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) + @property def file_name(self): return self._file_name @@ -946,17 +962,21 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= "Cannot provide both file_name and pyfits header", file_name=file_name, header=header) hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) - header = hdu.header - if header is None: - raise GalSimIncompatibleValuesError( - "Must provide either file_name or header", file_name=file_name, header=header) + try: + if file_name is not None: + header = hdu.header - # Read the wcs information from the header. - self._read_header(header) + if header is None: + raise GalSimIncompatibleValuesError( + "Must provide either file_name or header", file_name=file_name, header=header) - if file_name is not None: - fits.closeHDUList(hdu_list, fin) + # Read the wcs information from the header. + self._read_header(header) + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) if origin is not None: self.crpix += [ origin.x, origin.y ] @@ -978,11 +998,11 @@ def _read_header(self, header): elif ctype1.startswith('RA---') and ctype2.startswith('DEC--'): flip = False else: - raise GalSimError("The WCS read in does not define a pair of celestial axes. " - "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%( - ctype1, ctype2)) + raise GalSimError( + "GSFitsWCS only supports celestial coordinate systems." + "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%(ctype1, ctype2)) if ctype1[5:] != ctype2[5:]: - raise GalSimError("ctype1, ctype2 do not seem to agree on the WCS type") + raise OSError("ctype1, ctype2 do not seem to agree on the WCS type") self.wcs_type = ctype1[5:] if self.wcs_type in ('TAN', 'TPV', 'TNX', 'TAN-SIP'): self.projection = 'gnomonic' @@ -1161,7 +1181,7 @@ def _read_tnx(self, header): wat1[2] != 'lngcor' or wat1[3] != '=' or not wat1[4].startswith('"') or - not wat1[-1].endswith('"') ): + not wat1[-1].endswith('"') ): # pragma: no cover raise GalSimError("TNX WAT1 was not as expected") if ( len(wat2) < 12 or wat2[0] != 'wtype=tnx' or @@ -1169,7 +1189,7 @@ def _read_tnx(self, header): wat2[2] != 'latcor' or wat2[3] != '=' or not wat2[4].startswith('"') or - not wat2[-1].endswith('"') ): + not wat2[-1].endswith('"') ): # pragma: no cover raise GalSimError("TNX WAT2 was not as expected") # Break the next bit out into another function, since it is the same for x and y. @@ -1199,18 +1219,18 @@ def _parse_tnx_data(self, data): data = data[1:] else: data[0] = data[0][1:] - if data[-1] == '"': # pragma: no branch + if data[-1] == '"': data = data[:-1] - else: + else: # pragma: no cover data[-1] = data[-1][:-1] code = int(data[0].strip('.')) # Weirdly, these integers are given with decimal points. xorder = int(data[1].strip('.')) yorder = int(data[2].strip('.')) cross = int(data[3].strip('.')) - if cross != 2: + if cross != 2: # pragma: no cover raise NotImplementedError("TNX only implemented for half-cross option.") - if xorder != 4 or yorder != 4: + if xorder != 4 or yorder != 4: # pragma: no cover raise NotImplementedError("TNX only implemented for order = 4") # Note: order = 4 really means cubic. order is how large the pv matrix is, i.e. 4x4. @@ -1220,7 +1240,7 @@ def _parse_tnx_data(self, data): ymax = float(data[7]) pv1 = [ float(x) for x in data[8:] ] - if len(pv1) != 10: + if len(pv1) != 10: # pragma: no cover raise GalSimError("Wrong number of items found in WAT data") # Put these into our matrix formulation. @@ -1388,13 +1408,11 @@ def _xy(self, ra, dec, color=None): return x, y # Override the version in CelestialWCS, since we can do this more efficiently. - def _local(self, image_pos, world_pos, color=None): + def _local(self, image_pos, color=None): + from .wcs import JacobianWCS + if image_pos is None: - if world_pos is None: - raise GalSimIncompatibleValuesError( - "Either image_pos or world_pos must be provided", - image_pos=image_pos, world_pos=world_pos) - image_pos = self._posToImage(world_pos, color=color) + raise TypeError("origin must be a PositionD or PositionI argument") # The key lemma here is that chain rule for jacobians is just matrix multiplication. # i.e. if s = s(u,v), t = t(u,v) and u = u(x,y), v = v(x,y), then @@ -1688,6 +1706,8 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', (Note: this is set to True when this function is implicitly called from one of the galsim.fits.read* functions.) """ + from .wcs import AffineTransform, PixelScale, OffsetWCS + if file_name is not None: if header is not None: raise GalSimIncompatibleValuesError( @@ -1701,6 +1721,18 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', raise GalSimIncompatibleValuesError( "Must provide either file_name or header", file_name=file_name, header=header) + # For linear WCS specifications, AffineTransformation should work. + if header.get('CTYPE1', 'LINEAR') == 'LINEAR': + wcs = AffineTransform._readHeader(header) + # Convert to PixelScale if possible. + if (wcs.dudx == wcs.dvdy and wcs.dudy == wcs.dvdx == 0): + if wcs.x0 == wcs.y0 == wcs.u0 == wcs.v0 == 0: + wcs = PixelScale(wcs.dudx) + else: + wcs = OffsetWCS(wcs.dudx, wcs.origin, wcs.world_origin) + return wcs + + # Otherwise (and typically), try the various wcs types that can read celestial coordinates. for wcs_type in fits_wcs_types: try: wcs = wcs_type._readHeader(header) @@ -1716,6 +1748,8 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', if compression is not 'auto': wcs._tag += ', compression=%r'%compression return wcs + except KeyboardInterrupt: + raise except Exception as err: pass else: # pragma: no cover @@ -1725,8 +1759,7 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', warnings.warn("All the fits WCS types failed to read %r. Using AffineTransform " "instead, which will not really be correct."%(file_name), GalSimWarning) - wcs = AffineTransform._readHeader(header) - return wcs + return AffineTransform._readHeader(header) # Let this function work like a class in config. FitsWCS._req_params = { "file_name" : str } diff --git a/galsim/wcs.py b/galsim/wcs.py index 800334f8907..f232d0251af 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -50,7 +50,7 @@ import numpy as np from .gsobject import GSObject -from .position import PositionI, PositionD +from .position import Position, PositionI, PositionD from .celestial import CelestialCoord from .shear import Shear from .errors import GalSimError, GalSimIncompatibleValuesError @@ -233,10 +233,8 @@ def posToWorld(self, image_pos, color=None, **kwargs): CelestialCoord.project for the valid options. [default: 'gnomonic'] """ if color is None: color = self._color - if isinstance(image_pos, PositionI): - image_pos = PositionD(image_pos.x, image_pos.y) - elif not isinstance(image_pos, PositionD): - raise TypeError("toWorld requires a PositionD or PositionI argument") + if not isinstance(image_pos, Position): + raise TypeError("image_pos must be a PositionD or PositionI argument") return self._posToWorld(image_pos, color=color, **kwargs) def profileToWorld(self, image_profile, image_pos=None, world_pos=None, color=None): @@ -287,11 +285,9 @@ def posToImage(self, world_pos, color=None): """ if color is None: color = self._color if self.isCelestial() and not isinstance(world_pos, CelestialCoord): - raise TypeError("toImage requires a CelestialCoord argument") - elif not self.isCelestial() and isinstance(world_pos, PositionI): - world_pos = PositionD(world_pos.x, world_pos.y) - elif not self.isCelestial() and not isinstance(world_pos, PositionD): - raise TypeError("toImage requires a PositionD or PositionI argument") + raise TypeError("world_pos must be a CelestialCoord argument") + elif not self.isCelestial() and not isinstance(world_pos, Position): + raise TypeError("world_pos must be a PositionD or PositionI argument") return self._posToImage(world_pos, color=color) def profileToImage(self, world_profile, image_pos=None, world_pos=None, color=None): @@ -409,11 +405,15 @@ def local(self, image_pos=None, world_pos=None, color=None): @returns a LocalWCS instance. """ if color is None: color = self._color - if image_pos and world_pos: - raise GalSimIncompatibleValuesError( - "Only one of image_pos or world_pos may be provided", - image_pos=image_pos, world_pos=world_pos) - return self._local(image_pos, world_pos, color) + if world_pos is not None: + if image_pos is not None: + raise GalSimIncompatibleValuesError( + "Only one of image_pos or world_pos may be provided", + image_pos=image_pos, world_pos=world_pos) + image_pos = self.posToImage(world_pos, color) + if image_pos is not None and not isinstance(image_pos, Position): + raise TypeError("image_pos must be a PositionD or PositionI argument") + return self._local(image_pos, color) def jacobian(self, image_pos=None, world_pos=None, color=None): """Return the local JacobianWCS of the WCS at a given point. @@ -542,9 +542,7 @@ def withOrigin(self, origin, world_origin=None, color=None): @returns the new recentered WCS """ if color is None: color = self._color - if isinstance(origin, PositionI): - origin = PositionD(origin.x, origin.y) - elif not isinstance(origin, PositionD): + if not isinstance(origin, Position): raise TypeError("origin must be a PositionD or PositionI argument") return self._withOrigin(origin, world_origin, color) @@ -588,13 +586,11 @@ def writeToFitsHeader(self, header, bounds): 5. We haven't thought much about the security implications of this, so beware using GalSim to open FITS files from untrusted sources. - @param header A FitsHeader object to write the data to. + @param header A FitsHeader (or dict-like) object to write the data to. @param bounds The bounds of the image. """ from . import fits # First write the XMIN, YMIN values - if not isinstance(header, fits.FitsHeader): - header = fits.FitsHeader(header) header["GS_XMIN"] = (bounds.xmin, "GalSim image minimum x coordinate") header["GS_YMIN"] = (bounds.ymin, "GalSim image minimum y coordinate") @@ -637,15 +633,13 @@ def _set_origin(self, origin, world_origin=None): if origin is None: self._origin = PositionD(0,0) else: - if isinstance(origin, PositionI): - origin = PositionD(origin) - elif not isinstance(origin, PositionD): + if not isinstance(origin, Position): raise TypeError("origin must be a PositionD or PositionI argument") self._origin = origin if world_origin is None: self._world_origin = PositionD(0,0) else: - if not isinstance(world_origin, PositionD): + if not isinstance(world_origin, Position): raise TypeError("world_origin must be a PositionD argument") self._world_origin = world_origin @@ -702,20 +696,9 @@ def readFromFitsHeader(header): if wcs_name is not None: wcs_type = eval('galsim.' + wcs_name) wcs = wcs_type._readHeader(header) - elif 'GS_SCALE' in header: - # Old versions of GalSim didn't write GS_WCS, but did write GS_SCALE, which implies that - # the wcs is just a PixelScale: - wcs = PixelScale(header['GS_SCALE']) - elif 'CTYPE1' in header: - try: - wcs = FitsWCS(header=header, suppress_warning=True) - except KeyboardInterrupt: - raise - except: # pragma: no cover - # This shouldn't ever happen, but just in case... - wcs = PixelScale(1.) else: - wcs = PixelScale(1.) + # If we aren't told which type to use, this should find something appropriate + wcs = FitsWCS(header=header, suppress_warning=True) if xmin != 1 or ymin != 1: # ds9 always assumes the image has an origin at (1,1), so convert back to actual @@ -821,9 +804,7 @@ def _withOrigin(self, origin, world_origin, color): # v1 = v0 + v2 - v(x0,y0) else: - if isinstance(world_origin, PositionI): - world_origin = PositionD(world_origin.x, world_origin.y) - elif not isinstance(origin, PositionD): + if not isinstance(world_origin, Position): raise TypeError("world_origin must be a PositionD or PositionI argument") if not self.isLocal(): world_origin += self.world_origin - self._posToWorld(self.origin, color=color) @@ -831,11 +812,10 @@ def _withOrigin(self, origin, world_origin, color): # If the class doesn't define something else, then we can approximate the local Jacobian # from finite differences for the derivatives. This will be overridden by UniformWCS. - def _local(self, image_pos, world_pos, color): + def _local(self, image_pos, color): + if image_pos is None: - if world_pos is None: - raise TypeError("Either image_pos or world_pos must be provided") - image_pos = self._posToImage(world_pos, color=color) + raise TypeError("origin must be a PositionD or PositionI argument") # Calculate the Jacobian using finite differences for the derivatives. x0 = image_pos.x - self.x0 @@ -917,7 +897,7 @@ def _y(self, u, v, color=None): return self._local_wcs._y(u,v) # For UniformWCS, the local WCS is an attribute. Just return it. - def _local(self, image_pos=None, world_pos=None, color=None): + def _local(self, image_pos, color): return self._local_wcs # UniformWCS transformations can be inverted easily, so might as well provide that function. @@ -970,7 +950,7 @@ def _posToImage(self, world_pos, color): return PositionD(self._x(u,v),self._y(u,v)) # For LocalWCS, this is of course trivial. - def _local(self, image_pos, world_pos, color): + def _local(self, image_pos, color): return self @@ -1007,12 +987,11 @@ def _withOrigin(self, origin, world_origin, color): # from finite differences for the derivatives of ra and dec. Very similar to the # version for EuclideanWCS, but convert from dra, ddec to du, dv locallat at the given # position. - def _local(self, image_pos, world_pos, color): + def _local(self, image_pos, color): from .angle import radians, arcsec + if image_pos is None: - if world_pos is None: - raise TypeError("Either image_pos or world_pos must be provided") - image_pos = self._posToImage(world_pos, color) + raise TypeError("origin must be a PositionD or PositionI argument") x0 = image_pos.x - self.x0 y0 = image_pos.y - self.y0 @@ -1285,11 +1264,6 @@ def scale(self): return self._scale @property def shear(self): return self._shear - @property - def origin(self): return PositionD(0,0) - @property - def world_origin(self): return PositionD(0,0) - def _u(self, x, y, color=None): u = x * (1.-self._g1) - y * self._g2 u *= self._gfactor * self._scale @@ -1423,11 +1397,6 @@ def dvdx(self): return self._dvdx @property def dvdy(self): return self._dvdy - @property - def origin(self): return PositionD(0,0) - @property - def world_origin(self): return PositionD(0,0) - def _u(self, x, y, color=None): return self._dudx * x + self._dudy * y @@ -1439,10 +1408,16 @@ def _x(self, u, v, color=None): # ( dvdx dvdy ) # J^-1 = (1/det) ( dvdy -dudy ) # ( -dvdx dudx ) - return (self._dvdy * u - self._dudy * v)/self._det + try: + return (self._dvdy * u - self._dudy * v)/self._det + except ZeroDivisionError: + raise GalSimError("Transformation is singular") def _y(self, u, v, color=None): - return (-self._dvdx * u + self._dudx * v)/self._det + try: + return (-self._dvdx * u + self._dudx * v)/self._det + except ZeroDivisionError: + raise GalSimError("Transformation is singular") def _profileToWorld(self, image_profile): from .transform import _Transform @@ -1451,10 +1426,13 @@ def _profileToWorld(self, image_profile): def _profileToImage(self, world_profile): from .transform import _Transform - return _Transform(world_profile, - (self._dvdy/self._det, -self._dudy/self._det, - -self._dvdx/self._det, self._dudx/self._det), - flux_ratio=self._pixelArea()) + try: + return _Transform(world_profile, + (self._dvdy/self._det, -self._dudy/self._det, + -self._dvdx/self._det, self._dudx/self._det), + flux_ratio=self._pixelArea()) + except ZeroDivisionError: + raise GalSimError("Transformation is singular") def _pixelArea(self): return abs(self._det) @@ -1564,8 +1542,11 @@ def _maxScale(self): return 0.5 * (h1 + h2) def _inverse(self): - return JacobianWCS(self._dvdy/self._det, -self._dudy/self._det, - -self._dvdx/self._det, self._dudx/self._det) + try: + return JacobianWCS(self._dvdy/self._det, -self._dudy/self._det, + -self._dvdx/self._det, self._dudx/self._det) + except ZeroDivisionError: + raise GalSimError("Transformation is singular") def _toJacobian(self): return self @@ -1871,7 +1852,7 @@ def _readHeader(header): dudy = header.get("CD1_2",0.) dvdx = header.get("CD2_1",0.) dvdy = header.get("CD2_2",1.) - elif 'CDELT1' in header or 'CDELT2' in header: + else: dudx = header.get("CDELT1",1.) dudy = 0. dvdx = 0. @@ -1953,18 +1934,10 @@ def _writeFuncToHeader(func, letter, header): import pickle import types, marshal, base64 if type(func) == types.FunctionType: - try: - # Python3 and usually Python2 - code = marshal.dumps(func.__code__) - name = func.__name__ - defaults = func.__defaults__ - closure = func.__closure__ - except AttributeError: # pragma: no cover - # Older Python2 syntax, just in case. - code = marshal.dumps(func.func_code) - name = func.func_name - defaults = func.func_defaults - closure = func.func_closure + code = marshal.dumps(func.__code__) + name = func.__name__ + defaults = func.__defaults__ + closure = func.__closure__ # Functions may also have something called closure cells. If there are any, we need # to include them as well. Help for this part came from: @@ -2018,7 +1991,8 @@ def _writeFuncToHeader(func, letter, header): else: key = 'GS_%s%04d'%(letter,i) header[key] = s_array[i] -def _makecell(value): +def _makecell(value): # pragma: no cover + # (codecov gets confused, because the lambda function is never called.) # This is a little trick to make a closure cell. # We make a function that has the given value in closure, then then get the # first (only) closure item, which will be the closure cell we need. diff --git a/tests/test_wcs.py b/tests/test_wcs.py index ca793586581..a0b3053a07c 100644 --- a/tests/test_wcs.py +++ b/tests/test_wcs.py @@ -177,24 +177,38 @@ def do_wcs_pos(wcs, ufunc, vfunc, name, x0=0, y0=0, color=None): image_pos = galsim.PositionD(x+x0,y+y0) world_pos = galsim.PositionD(u,v) world_pos2 = wcs.toWorld(image_pos, color=color) + world_pos3 = wcs.posToWorld(image_pos, color=color) np.testing.assert_almost_equal( world_pos.x, world_pos2.x, digits2, 'wcs.toWorld returned wrong world position for '+name) np.testing.assert_almost_equal( world_pos.y, world_pos2.y, digits2, 'wcs.toWorld returned wrong world position for '+name) + np.testing.assert_almost_equal( + world_pos.x, world_pos3.x, digits2, + 'wcs.postoWorld returned wrong world position for '+name) + np.testing.assert_almost_equal( + world_pos.y, world_pos3.y, digits2, + 'wcs.postoWorld returned wrong world position for '+name) scale = wcs.maxLinearScale(image_pos, color=color) try: # The reverse transformation is not guaranteed to be implemented, # so guard against NotImplementedError being raised: image_pos2 = wcs.toImage(world_pos, color=color) + image_pos3 = wcs.posToImage(world_pos, color=color) np.testing.assert_almost_equal( image_pos.x*scale, image_pos2.x*scale, digits2, 'wcs.toImage returned wrong image position for '+name) np.testing.assert_almost_equal( image_pos.y*scale, image_pos2.y*scale, digits2, 'wcs.toImage returned wrong image position for '+name) + np.testing.assert_almost_equal( + image_pos.x*scale, image_pos3.x*scale, digits2, + 'wcs.posToImage returned wrong image position for '+name) + np.testing.assert_almost_equal( + image_pos.y*scale, image_pos3.y*scale, digits2, + 'wcs.posToImage returned wrong image position for '+name) except NotImplementedError: pass @@ -207,6 +221,21 @@ def do_wcs_pos(wcs, ufunc, vfunc, name, x0=0, y0=0, color=None): np.testing.assert_almost_equal( world_pos.y, wcs.toWorld(image_pos, color=color).y, digits2, 'wcs.toWorld gave different value with PositionI image_pos for '+name) + assert_raises(TypeError, wcs.posToWorld, (3,4)) + assert_raises(TypeError, wcs.toWorld, (3,4)) + assert_raises(TypeError, wcs.toWorld, galsim.CelestialCoord(0*galsim.degrees,0*galsim.degrees)) + assert_raises(TypeError, wcs.posToWorld, + galsim.CelestialCoord(0*galsim.degrees,0*galsim.degrees)) + assert_raises(TypeError, wcs.toImage, (3,4)) + assert_raises(TypeError, wcs.posToImage, (3,4)) + if wcs.isCelestial(): + assert_raises(TypeError, wcs.toImage, galsim.PositionD(3,4)) + assert_raises(TypeError, wcs.posToImage, galsim.PositionD(3,4)) + else: + assert_raises(TypeError, wcs.toImage, + galsim.CelestialCoord(0*galsim.degrees,0*galsim.degrees)) + assert_raises(TypeError, wcs.posToImage, + galsim.CelestialCoord(0*galsim.degrees,0*galsim.degrees)) def check_world(pos1, pos2, digits, err_msg): @@ -271,9 +300,7 @@ def do_wcs_image(wcs, name, approx=False): if wcs.isUniform(): # Test that the regular CD, CRPIX, CRVAL items that are written to the header # describe an equivalent WCS as this one. - hdu, hdu_list, fin = galsim.fits.readFile(test_name, dir=dir) - affine = galsim.AffineTransform._readHeader(hdu.header) - galsim.fits.closeHDUList(hdu_list, fin) + affine = galsim.FitsWCS(test_name, dir=dir) check_world(affine.toWorld(im.origin), world1, digits2, "World position of origin is wrong after write/read.") check_world(affine.toWorld(im.center), world2, digits2, @@ -567,17 +594,15 @@ def do_nonlocal_wcs(wcs, ufunc, vfunc, name, test_pickle=True, color=None): np.testing.assert_almost_equal( world_pos2.y, world_pos1.y, digits, 'withOrigin(new_origin) returned wrong world position') - if not wcs.isCelestial(): - new_world_origin = galsim.PositionD(5352.7, 9234.3) - wcs5 = wcs.withOrigin(new_origin, new_world_origin, color=color) - world_pos3 = wcs5.toWorld(new_origin, color=color) - np.testing.assert_almost_equal( - world_pos3.x, new_world_origin.x, digits, - 'withOrigin(new_origin, new_world_origin) returned wrong position') - np.testing.assert_almost_equal( - world_pos3.y, new_world_origin.y, digits, - 'withOrigin(new_origin, new_world_origin) returned wrong position') - + new_world_origin = galsim.PositionD(5352.7, 9234.3) + wcs5 = wcs.withOrigin(new_origin, new_world_origin, color=color) + world_pos3 = wcs5.toWorld(new_origin, color=color) + np.testing.assert_almost_equal( + world_pos3.x, new_world_origin.x, digits, + 'withOrigin(new_origin, new_world_origin) returned wrong position') + np.testing.assert_almost_equal( + world_pos3.y, new_world_origin.y, digits, + 'withOrigin(new_origin, new_world_origin) returned wrong position') # Check that (x,y) -> (u,v) and converse work correctly # These tests work regardless of whether the WCS is local or not. @@ -665,6 +690,27 @@ def do_nonlocal_wcs(wcs, ufunc, vfunc, name, test_pickle=True, color=None): im1.array, im2.array, digits, 'world_profile with given wcs and image_profile differed when drawn for '+name) + # Check some properties that should be the same for the wcs and its local jacobian. + np.testing.assert_allclose( + wcs.minLinearScale(image_pos=image_pos, color=color), + wcs.jacobian(image_pos=image_pos, color=color).minLinearScale(color=color)) + np.testing.assert_allclose( + wcs.maxLinearScale(image_pos=image_pos, color=color), + wcs.jacobian(image_pos=image_pos, color=color).maxLinearScale(color=color)) + np.testing.assert_allclose( + wcs.pixelArea(image_pos=image_pos, color=color), + wcs.jacobian(image_pos=image_pos, color=color).pixelArea(color=color)) + + if not wcs.isUniform(): + assert_raises(TypeError, wcs.local) + assert_raises(TypeError, wcs.local, image_pos=image_pos, world_pos=world_pos, color=color) + assert_raises(TypeError, wcs.local, image_pos=(3,4), color=color) + assert_raises(TypeError, wcs.local, world_pos=(3,4), color=color) + + assert_raises(TypeError, wcs.withOrigin) + assert_raises(TypeError, wcs.withOrigin, origin=(3,4), color=color) + assert_raises(TypeError, wcs.withOrigin, origin=image_pos, world_origin=(3,4), color=color) + def do_celestial_wcs(wcs, name, test_pickle=True): # It's a bit harder to test WCS functions that return a CelestialCoord, since @@ -778,6 +824,16 @@ def do_celestial_wcs(wcs, name, test_pickle=True): except NotImplementedError: pass + assert_raises(TypeError, wcs.local) + assert_raises(TypeError, wcs.local, image_pos=image_pos, world_pos=world_pos) + assert_raises(TypeError, wcs.local, image_pos=(3,4)) + assert_raises(TypeError, wcs.local, world_pos=(3,4)) + + assert_raises(TypeError, wcs.withOrigin) + assert_raises(TypeError, wcs.withOrigin, origin=(3,4)) + assert_raises(TypeError, wcs.withOrigin, world_origin=(3,4)) + assert_raises(TypeError, wcs.withOrigin, origin=image_pos, world_origin=world_pos) + @timer def test_pixelscale(): @@ -791,6 +847,14 @@ def test_pixelscale(): assert wcs == wcs2, 'PixelScale copy is not == the original' wcs3 = galsim.PixelScale(scale + 0.1234) assert wcs != wcs3, 'PixelScale is not != a different one' + assert wcs.scale == scale + assert wcs.origin == galsim.PositionD(0,0) + assert wcs.world_origin == galsim.PositionD(0,0) + + assert_raises(TypeError, galsim.PixelScale) + assert_raises(TypeError, galsim.PixelScale, scale=galsim.PixelScale(scale)) + assert_raises(TypeError, galsim.PixelScale, scale=scale, origin=galsim.PositionD(0,0)) + assert_raises(TypeError, galsim.PixelScale, scale=scale, world_origin=galsim.PositionD(0,0)) ufunc = lambda x,y: x*scale vfunc = lambda x,y: y*scale @@ -818,10 +882,25 @@ def test_pixelscale(): # Add an image origin offset x0 = 1 y0 = 1 - origin = galsim.PositionD(x0,y0) + origin = galsim.PositionI(x0,y0) wcs = galsim.OffsetWCS(scale, origin) wcs2 = galsim.PixelScale(scale).withOrigin(origin) assert wcs == wcs2, 'OffsetWCS is not == PixelScale.withOrigin(origin)' + assert wcs.origin == origin + assert wcs.scale == scale + + # Default origin is (0,0) + wcs3 = galsim.OffsetWCS(scale) + assert wcs3.origin == galsim.PositionD(0,0) + assert wcs3.world_origin == galsim.PositionD(0,0) + + assert_raises(TypeError, galsim.OffsetWCS) + assert_raises(TypeError, galsim.OffsetWCS, scale=galsim.PixelScale(scale)) + assert_raises(TypeError, galsim.OffsetWCS, scale=scale, origin=5) + assert_raises(TypeError, galsim.OffsetWCS, scale=scale, + origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + assert_raises(TypeError, galsim.OffsetWCS, scale=scale, + world_origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) # Check basic copy and == , != for OffsetWCS: wcs2 = wcs.copy() @@ -871,6 +950,15 @@ def test_shearwcs(): g2 = -0.37 shear = galsim.Shear(g1=g1,g2=g2) wcs = galsim.ShearWCS(scale, shear) + assert wcs.shear == shear + assert wcs.origin == galsim.PositionD(0,0) + assert wcs.world_origin == galsim.PositionD(0,0) + + assert_raises(TypeError, galsim.ShearWCS) + assert_raises(TypeError, galsim.ShearWCS, shear=0.3) + assert_raises(TypeError, galsim.ShearWCS, shear=shear, origin=galsim.PositionD(0,0)) + assert_raises(TypeError, galsim.ShearWCS, shear=shear, world_origin=galsim.PositionD(0,0)) + assert_raises(TypeError, galsim.ShearWCS, g1=g1, g2=g2) # Check basic copy and == , !=: wcs2 = wcs.copy() @@ -911,6 +999,21 @@ def test_shearwcs(): wcs = galsim.OffsetShearWCS(scale, shear, origin) wcs2 = galsim.ShearWCS(scale, shear).withOrigin(origin) assert wcs == wcs2, 'OffsetShearWCS is not == ShearWCS.withOrigin(origin)' + assert wcs.shear == shear + assert wcs.origin == origin + assert wcs.world_origin == galsim.PositionD(0,0) + + wcs3 = galsim.OffsetShearWCS(scale, shear) + assert wcs3.origin == galsim.PositionD(0,0) + assert wcs3.world_origin == galsim.PositionD(0,0) + + assert_raises(TypeError, galsim.OffsetShearWCS) + assert_raises(TypeError, galsim.OffsetShearWCS, shear=0.3) + assert_raises(TypeError, galsim.OffsetShearWCS, shear=shear, origin=5) + assert_raises(TypeError, galsim.OffsetShearWCS, shear=shear, + origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + assert_raises(TypeError, galsim.OffsetShearWCS, shear=shear, + world_origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) # Check basic copy and == , != for OffsetShearWCS: wcs2 = wcs.copy() @@ -965,6 +1068,18 @@ def test_affinetransform(): wcs = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) + assert wcs.dudx == dudx + assert wcs.dudy == dudy + assert wcs.dvdx == dvdx + assert wcs.dvdy == dvdy + + assert_raises(TypeError, galsim.JacobianWCS) + assert_raises(TypeError, galsim.JacobianWCS, dudx, dudy, dvdx) + assert_raises(TypeError, galsim.JacobianWCS, dudx, dudy, dvdx, dvdy, + origin=galsim.PositionD(0,0)) + assert_raises(TypeError, galsim.JacobianWCS, dudx, dudy, dvdx, dvdy, + world_origin=galsim.PositionD(0,0)) + # Check basic copy and == , !=: wcs2 = wcs.copy() assert wcs == wcs2, 'JacobianWCS copy is not == the original' @@ -992,6 +1107,14 @@ def test_affinetransform(): wcs2 = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy).withOrigin(origin) assert wcs == wcs2, 'AffineTransform is not == JacobianWCS.withOrigin(origin)' + assert_raises(TypeError, galsim.AffineTransform) + assert_raises(TypeError, galsim.AffineTransform, dudx, dudy, dvdx) + assert_raises(TypeError, galsim.AffineTransform, dudx, dudy, dvdx, dvdy, origin=3) + assert_raises(TypeError, galsim.AffineTransform, dudx, dudy, dvdx, dvdy, + origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + assert_raises(TypeError, galsim.AffineTransform, dudx, dudy, dvdx, dvdy, + world_origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + # Check basic copy and == , != for AffineTransform: wcs2 = wcs.copy() assert wcs == wcs2, 'AffineTransform copy is not == the original' @@ -1066,6 +1189,18 @@ def test_affinetransform(): # Check that using a wcs in the context of an image works correctly do_wcs_image(wcs, 'AffineTransform') + # Degenerate transformation should raise some errors + degen_wcs = galsim.JacobianWCS(0.2, 0.1, 0.2, 0.1) + assert_raises(galsim.GalSimError, degen_wcs.getDecomposition) + + image_pos = galsim.PositionD(0,0) + world_pos = degen_wcs.toWorld(image_pos) # This direction is ok. + assert_raises(galsim.GalSimError, degen_wcs.toImage, world_pos) # This is not. + assert_raises(galsim.GalSimError, degen_wcs._x, 0, 0) + assert_raises(galsim.GalSimError, degen_wcs._y, 0, 0) + assert_raises(galsim.GalSimError, degen_wcs.inverse) + assert_raises(galsim.GalSimError, degen_wcs.toImage, galsim.Gaussian(sigma=2)) + def radial_u(x, y): """A cubic radial function used for a u(x,y) function """ @@ -1120,12 +1255,29 @@ def test_uvfunction(): vfunc = lambda x,y: y * scale wcs = galsim.UVFunction(ufunc, vfunc) do_nonlocal_wcs(wcs, ufunc, vfunc, 'UVFunction like PixelScale', test_pickle=False) + assert wcs.ufunc(2.9, 3.7) == ufunc(2.9, 3.7) + assert wcs.vfunc(2.9, 3.7) == vfunc(2.9, 3.7) + assert wcs.xfunc is None + assert wcs.yfunc is None # Also check with inverse functions. xfunc = lambda u,v: u / scale yfunc = lambda u,v: v / scale wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc) do_nonlocal_wcs(wcs, ufunc, vfunc, 'UVFunction like PixelScale with inverse', test_pickle=False) + assert wcs.ufunc(2.9, 3.7) == ufunc(2.9, 3.7) + assert wcs.vfunc(2.9, 3.7) == vfunc(2.9, 3.7) + assert wcs.xfunc(2.9, 3.7) == xfunc(2.9, 3.7) + assert wcs.yfunc(2.9, 3.7) == yfunc(2.9, 3.7) + + assert_raises(TypeError, galsim.UVFunction) + assert_raises(TypeError, galsim.UVFunction, ufunc=ufunc) + assert_raises(TypeError, galsim.UVFunction, vfunc=vfunc) + assert_raises(TypeError, galsim.UVFunction, ufunc, vfunc, origin=5) + assert_raises(TypeError, galsim.UVFunction, ufunc, vfunc, + origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + assert_raises(TypeError, galsim.UVFunction, ufunc, vfunc, + world_origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) # 2. Like ShearWCS scale = 0.23 @@ -1279,6 +1431,19 @@ def test_uvfunction(): wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc) do_nonlocal_wcs(wcs, ufunc, vfunc, 'UVFunction from demo9', test_pickle=False) + # Check that passing really long strings works correctly. + ufuncs = "0.05 * x * (1. + 2.e-6 * (x**2 + y**2))" + vfuncs = "0.05 * y * (1. + 2.e-6 * (x**2 + y**2))" + xfuncs = ("(lambda w: ( 0. if w==0. else " + " 100.*u/w*(( 5*math.sqrt(w**2+5.e3/27.)+5*w )**(1./3.) - " + " ( 5*math.sqrt(w**2+5.e3/27.)-5*w )**(1./3.))) )(math.sqrt(u**2+v**2))") + yfuncs = ("(lambda w: ( 0. if w==0. else " + " 100.*v/w*(( 5*math.sqrt(w**2+5.e3/27.)+5*w )**(1./3.) - " + " ( 5*math.sqrt(w**2+5.e3/27.)-5*w )**(1./3.))) )(math.sqrt(u**2+v**2))") + wcs = galsim.UVFunction(ufuncs, vfuncs, xfuncs, yfuncs) + do_nonlocal_wcs(wcs, ufunc, vfunc, 'UVFunction from demo9, string', test_pickle=True) + do_wcs_image(wcs, 'UVFunction from demo9, string') + # This version doesn't work with numpy arrays because of the math functions. # This provides a test of that branch of the makeSkyImage function. ufunc = lambda x,y : 0.17 * x * (1. + 1.e-5 * math.sqrt(x**2 + y**2)) @@ -1296,7 +1461,7 @@ def test_uvfunction(): do_nonlocal_wcs(wcs, lambda x,y: ufunc(x,y,-0.3), lambda x,y: vfunc(x,y,-0.3), 'UVFunction with color-dependence', test_pickle=False, color=-0.3) - # Check that passing functions as strings works correctly. + # Also, check this one as a string wcs = galsim.UVFunction(ufunc='(%r+0.1*c)*x + %r*y'%(dudx,dudy), vfunc='%r*x + (%r-0.2*c)*y'%(dvdx,dvdy), xfunc='((%r-0.2*c)*u - %r*v)/((%r+0.1*c)*(%r-0.2*c)-%r)'%( @@ -1401,6 +1566,10 @@ def test_radecfunction(): dec_str = '%r.deproject(x*galsim.arcsec,y*galsim.arcsec,projection="lambert").dec.rad'%center wcs5 = galsim.RaDecFunction(ra_str, dec_str, origin=galsim.PositionD(-9.,-8.)) + wcs6 = wcs2.copy() + assert wcs2 == wcs6, 'RaDecFunction copy is not == the original' + assert wcs6.radec_func(3,4) == radec_func(3,4) + # Check that distance, jacobian for some x,y positions match the UV values. for x,y in zip(far_x_list, far_y_list): @@ -1458,16 +1627,16 @@ def test_radecfunction(): # match pretty well. np.testing.assert_almost_equal( jac2.minLinearScale(), jac1.minLinearScale(), digits, - 'RaDecFunction '+name+' minScale() does not match expected value.') + 'RaDecFunction '+name+' minLinearScale() does not match expected value.') np.testing.assert_almost_equal( test_wcs.minLinearScale(image_pos), jac1.minLinearScale(), digits, - 'RaDecFunction '+name+' minScale(pos) does not match expected value.') + 'RaDecFunction '+name+' minLinearScale(pos) does not match expected value.') np.testing.assert_almost_equal( jac2.maxLinearScale(), jac1.maxLinearScale(), digits, - 'RaDecFunction '+name+' maxScale() does not match expected value.') + 'RaDecFunction '+name+' maxLinearScale() does not match expected value.') np.testing.assert_almost_equal( test_wcs.maxLinearScale(image_pos), jac1.maxLinearScale(), digits, - 'RaDecFunction '+name+' maxScale(pos) does not match expected value.') + 'RaDecFunction '+name+' maxLinearScale(pos) does not match expected value.') # The main discrepancy between the jacobians is a rotation term. # The pixels in the projected coordinates do not necessarily point north, @@ -1520,6 +1689,12 @@ def test_radecfunction(): do_celestial_wcs(wcs5, 'RaDecFunc 4 centered at '+str(center.ra/galsim.degrees)+ ', '+str(center.dec/galsim.degrees), test_pickle=True) + assert_raises(TypeError, galsim.RaDecFunction) + assert_raises(TypeError, galsim.RaDecFunction, radec_func, origin=5) + assert_raises(TypeError, galsim.RaDecFunction, radec_func, + origin=galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees)) + assert_raises(TypeError, galsim.RaDecFunction, radec_func, world_origin=galsim.PositionD(0,0)) + # Check that using a wcs in the context of an image works correctly # (Uses the last wcs2, wcs3 set in the above loops.) do_wcs_image(wcs2, 'RaDecFunction') @@ -1577,7 +1752,7 @@ def test_astropywcs(): import astropy.wcs import scipy # AstropyWCS constructor will do this, so check now. except ImportError as e: - print('Unable to import astropy.wcs. Skipping AstropyWCS tests.') + print('Unable to import astropy.wcs or scipy. Skipping AstropyWCS tests.') print('Caught ',e) return @@ -1586,13 +1761,16 @@ def test_astropywcs(): if __name__ == "__main__": test_tags = [ 'HPX', 'TAN', 'TSC', 'STG', 'ZEA', 'ARC', 'ZPN', 'SIP', 'TAN-FLIP', 'REGION' ] else: - test_tags = [ 'SIP' ] + test_tags = [ 'TAN', 'SIP' ] dir = 'fits_files' for tag in test_tags: file_name, ref_list = references[tag] print(tag,' file_name = ',file_name) - wcs = galsim.AstropyWCS(file_name, dir=dir) + if tag == 'TAN': + wcs = galsim.AstropyWCS(file_name, dir=dir, compression='none', hdu=0) + else: + wcs = galsim.AstropyWCS(file_name, dir=dir) do_ref(wcs, ref_list, 'AstropyWCS '+tag) @@ -1600,6 +1778,32 @@ def test_astropywcs(): do_wcs_image(wcs, 'AstropyWCS_'+tag) + # Can also use an existing astropy.wcs.WCS instance to construct. + # This is probably a rare use case, but could aid efficiency if you already build the + # astropy WCS for other purposes. + astropy_wcs = wcs.wcs # Just steal such an object from the last wcs above. + assert isinstance(astropy_wcs, astropy.wcs.WCS) + wcs1 = galsim.AstropyWCS(wcs=astropy_wcs) + do_celestial_wcs(wcs1, 'AstropyWCS from wcs', test_pickle=False) + repr(wcs1) + + # Can also use a header. Again steal it from the wcs above. + wcs2 = galsim.AstropyWCS(header=wcs.header) + do_celestial_wcs(wcs2, 'AstropyWCS from header', test_pickle=True) + + # Doesn't support LINEAR WCS types. + with assert_raises(galsim.GalSimError): + galsim.AstropyWCS('SBProfile_comparison_images/kolmogorov.fits') + + assert_raises(TypeError, galsim.AstropyWCS) + assert_raises(TypeError, galsim.AstropyWCS, file_name, header='dummy') + assert_raises(TypeError, galsim.AstropyWCS, file_name, wcs=wcs) + assert_raises(TypeError, galsim.AstropyWCS, wcs=wcs, header='dummy') + + # Astropy thinks it can handle ZPX files, but as of version 2.0.4, they don't work right. + # Check that we raise an exception for any attempt to read these with astropy. + # (And if they ever fix this, add 'ZPX' to the test_tags above and remvoe this check.) + assert_raises(galsim.GalSimError, galsim.AstropyWCS, references['ZPX'][0], dir=dir) @timer def test_pyastwcs(): @@ -1623,7 +1827,10 @@ def test_pyastwcs(): for tag in test_tags: file_name, ref_list = references[tag] print(tag,' file_name = ',file_name) - wcs = galsim.PyAstWCS(file_name, dir=dir) + if tag == 'TAN': + wcs = galsim.PyAstWCS(file_name, dir=dir, compression='none', hdu=0) + else: + wcs = galsim.PyAstWCS(file_name, dir=dir) # The PyAst implementation of the SIP type only gets the inverse transformation # approximately correct. So we need to be a bit looser in that check. @@ -1637,6 +1844,30 @@ def test_pyastwcs(): approx = tag in [ 'ZPX', 'TAN-FLIP' ] do_wcs_image(wcs, 'PyAstWCS_'+tag, approx) + # Can also use an existing startlink.Ast.FrameSet instance to construct. + # This is probably a rare use case, but could aid efficiency if you already open the + # fits file with starlink for other purposes. + wcs = galsim.PyAstWCS(references['TAN'][0], dir=dir) + wcsinfo = wcs.wcsinfo + assert isinstance(wcsinfo, starlink.Ast.FrameSet) + wcs1 = galsim.PyAstWCS(wcsinfo=wcsinfo) + do_celestial_wcs(wcs1, 'PyAstWCS from wcsinfo', test_pickle=False) + repr(wcs1) + + # Can also use a header. Again steal it from the wcs above. + wcs2 = galsim.PyAstWCS(header=wcs.header) + do_celestial_wcs(wcs2, 'PyAstWCS from header', test_pickle=True) + + # Doesn't support LINEAR WCS types. + with assert_raises(galsim.GalSimError): + galsim.PyAstWCS('SBProfile_comparison_images/kolmogorov.fits') + + assert_raises(TypeError, galsim.PyAstWCS) + assert_raises(TypeError, galsim.PyAstWCS, file_name, header='dummy') + assert_raises(TypeError, galsim.PyAstWCS, file_name, wcsinfo=wcsinfo) + assert_raises(TypeError, galsim.PyAstWCS, wcsinfo=wcsinfo, header='dummy') + + @timer def test_wcstools(): @@ -1680,6 +1911,18 @@ def test_wcstools(): do_wcs_image(wcs, 'WcsToolsWCS_'+tag) + # TAN-PV is one of the ones that WcsToolsWCS doesn't support. + #with assert_raises(galsim.GalSimValueError): + galsim.WcsToolsWCS(references['TAN-PV'][0], dir=dir) + + # Doesn't support LINEAR WCS types. + with assert_raises(galsim.GalSimError): + galsim.WcsToolsWCS('SBProfile_comparison_images/kolmogorov.fits') + + assert_raises(TypeError, galsim.WcsToolsWCS) + assert_raises(TypeError, galsim.WcsToolsWCS, file_name, header='dummy') + + @timer def test_gsfitswcs(): @@ -1695,7 +1938,11 @@ def test_gsfitswcs(): for tag in test_tags: file_name, ref_list = references[tag] print(tag,' file_name = ',file_name) - wcs = galsim.GSFitsWCS(file_name, dir=dir) + if tag == 'TAN': + # For this one, check compression and hdu options. + wcs = galsim.GSFitsWCS(file_name, dir=dir, compression='none', hdu=0) + else: + wcs = galsim.GSFitsWCS(file_name, dir=dir) do_ref(wcs, ref_list, 'GSFitsWCS '+tag) @@ -1703,6 +1950,22 @@ def test_gsfitswcs(): do_wcs_image(wcs, 'GSFitsWCS_'+tag) + # TSC is one of the ones that GSFitsWCS doesn't support. + with assert_raises(galsim.GalSimValueError): + galsim.GSFitsWCS(references['TSC'][0], dir=dir) + + # Doesn't support LINEAR WCS types. + with assert_raises(galsim.GalSimError): + galsim.GSFitsWCS('SBProfile_comparison_images/kolmogorov.fits') + + assert_raises(TypeError, galsim.GSFitsWCS) + assert_raises(TypeError, galsim.GSFitsWCS, file_name, header='dummy') + +@timer +def test_tanwcs(): + """Test the TanWCS function, which returns a GSFitsWCS instance. + """ + # Use TanWCS function to create TAN GSFitsWCS objects from scratch. # First a slight tweak on a simple scale factor dudx = 0.2342 @@ -1773,7 +2036,10 @@ def test_fitswcs(): for tag in test_tags: file_name, ref_list = references[tag] print(tag,' file_name = ',file_name) - wcs = galsim.FitsWCS(file_name, dir=dir, suppress_warning=True) + if tag == 'TAN': + wcs = galsim.FitsWCS(file_name, dir=dir, compression='none', hdu=0) + else: + wcs = galsim.FitsWCS(file_name, dir=dir, suppress_warning=True) print('FitsWCS is really ',type(wcs)) if isinstance(wcs, galsim.AffineTransform): @@ -1797,6 +2063,14 @@ def test_fitswcs(): affine = galsim.AffineTransform._readHeader(hdu.header) galsim.fits.closeHDUList(hdu_list, fin) + # This does support LINEAR WCS types. + linear = galsim.FitsWCS('SBProfile_comparison_images/kolmogorov.fits') + assert isinstance(linear, galsim.OffsetWCS) + + assert_raises(TypeError, galsim.FitsWCS) + assert_raises(TypeError, galsim.FitsWCS, file_name, header='dummy') + + @timer def test_scamp(): @@ -1989,6 +2263,7 @@ def test_coadd(): test_pyastwcs() test_wcstools() test_gsfitswcs() + test_tanwcs() test_fitswcs() test_scamp() test_compateq() From 8ec9e1ea7e23d85c88db333286664e7db78b2073 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 30 Apr 2018 20:12:56 -0400 Subject: [PATCH 58/96] Increase test coverage of scene (#755) --- CHANGELOG.md | 3 + galsim/config/input_cosmos.py | 35 +-- galsim/scene.py | 485 +++++++++++----------------------- galsim/utilities.py | 4 +- tests/test_scene.py | 314 ++++++++++++++-------- tests/test_utilities.py | 53 +++- 6 files changed, 422 insertions(+), 472 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 307e0d4d094..4c62b4cdaaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ Dependency Changes - Officially no longer support Python 2.6. (#755) - No longer support pre-astropy versions of pyfits (now bundled in astropy as astropy.io.fits). Nor astropy versions <1.0. (#755) +- No longer support pre-2016 version of the COSMOS catalog. You may be + asked to run galsim_download_cosmos again if your version is found to + be obsolete. (#755) - Added LSSTDESC.Coord, which contains the functionality that used to be in GalSim as the Angle and CelestialCoord classes. We moved it to a separate repo so people could more easily use this functionality without requiring all diff --git a/galsim/config/input_cosmos.py b/galsim/config/input_cosmos.py index 411e8d0eb19..ee8896bda93 100644 --- a/galsim/config/input_cosmos.py +++ b/galsim/config/input_cosmos.py @@ -71,24 +71,20 @@ def _BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger): if 'index' in config: galsim.config.SetDefaultIndex(config, cosmos_cat.getNObjects()) - kwargs, safe = galsim.config.GetAllParams(config, base, - req = galsim.COSMOSCatalog.makeGalaxy._req_params, - opt = galsim.COSMOSCatalog.makeGalaxy._opt_params, - single = galsim.COSMOSCatalog.makeGalaxy._single_params, - ignore = ignore) + opt = { "index" : int, + "gal_type" : str, + "noise_pad_size" : float, + "deep" : bool, + "sersic_prec": float, + "n_random": int + } + + kwargs, safe = galsim.config.GetAllParams(config, base, opt=opt, ignore=ignore) if gsparams: kwargs['gsparams'] = galsim.GSParams(**gsparams) - # Deal with defaults for gal_type, if it wasn't specified: - # If COSMOSCatalog was constructed with 'use_real'=True, then default is 'real'. Otherwise, the - # default is 'parametric'. This code is in makeGalaxy, but since config has to use - # _makeSingleGalaxy, we have to include this here too. - if 'gal_type' not in kwargs: - if cosmos_cat.use_real: kwargs['gal_type'] = 'real' - else: kwargs['gal_type'] = 'parametric' + rng = galsim.config.GetRNG(config, base, logger, 'COSMOSGalaxy') - rng = None if 'index' not in kwargs: - rng = galsim.config.GetRNG(config, base, logger, 'COSMOSGalaxy') kwargs['index'], n_rng_calls = cosmos_cat.selectRandomIndex(1, rng=rng, _n_rng_calls=True) # Make sure this process gives consistent results regardless of the number of processes @@ -98,12 +94,7 @@ def _BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger): # discard the same number of random calls from the one in the config dict. rng.discard(int(n_rng_calls)) - # Even though gal_type is optional, it will have been set in the code above. So we can at this - # point assume that kwargs['gal_type'] exists. - if kwargs['gal_type'] == 'real': - if rng is None: - rng = galsim.config.GetRNG(config, base, logger, 'COSMOSGalaxy') - kwargs['rng'] = rng + kwargs['rng'] = rng # NB. Even though index is officially optional, it will always be present, either because it was # set by a call to selectRandomIndex, explicitly by the user, or due to the call to @@ -115,12 +106,12 @@ def _BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger): logger.debug('obj %d: COSMOSGalaxy kwargs = %s',base.get('obj_num',0),kwargs) - kwargs['cosmos_catalog'] = cosmos_cat + kwargs['self'] = cosmos_cat # Use a staticmethod of COSMOSCatalog to avoid pickling the result of makeGalaxy() # The RealGalaxy in particular has a large serialization, so it is more efficient to # make it in this process, which is what happens here. - gal = galsim.COSMOSCatalog._makeSingleGalaxy(**kwargs) + gal = galsim.COSMOSCatalog._makeGalaxy(**kwargs) return gal, safe diff --git a/galsim/scene.py b/galsim/scene.py index 6842290b915..73f6121c661 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -174,12 +174,16 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, from .real import _parse_files_dirs self.use_real = use_real + # We'll set these up if and when we need them. + self._bandpass = None + self._sed = None + if exclusion_level not in ('none', 'bad_stamp', 'bad_fits', 'marginal'): raise GalSimValueError("Invalid value of exclusion_level.", exclusion_level, ('none', 'bad_stamp', 'bad_fits', 'marginal')) # Start by parsing the file name - full_file_name, _, self.use_sample = _parse_files_dirs(file_name, dir, sample) + self.full_file_name, _, self.use_sample = _parse_files_dirs(file_name, dir, sample) if self.use_real and not _nobjects_only: # First, do the easy thing: real galaxies. We make the galsim.RealGalaxyCatalog() @@ -189,37 +193,24 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # The fits name has _fits inserted before the .fits ending. # Note: don't just use k = -5 in case it actually ends with .fits.fz - k = self.real_cat.file_name.find('.fits') - param_file_name = self.real_cat.file_name[:k] + '_fits' + self.real_cat.file_name[k:] + param_file_name = self.real_cat.file_name.replace('.fits', '_fits.fits') with pyfits.open(param_file_name) as fits: self.param_cat = fits[1].data else: + self.real_cat = None try: # Read in data. - with pyfits.open(full_file_name) as fits: + with pyfits.open(self.full_file_name) as fits: self.param_cat = fits[1].data # Check if this was the right file. It should have a 'fit_status' column. self.param_cat['fit_status'] - except KeyError: # pragma: no cover + except KeyError: # But if that doesn't work, then the name might be the name of the real catalog, # so try adding _fits to it as above. - k = full_file_name.find('.fits') - param_file_name = full_file_name[:k] + '_fits' + full_file_name[k:] + param_file_name = self.full_file_name.replace('.fits', '_fits.fits') with pyfits.open(param_file_name) as fits: self.param_cat = fits[1].data - # Check for the old-style parameter catalog - if 'fit_dvc_btt' not in self.param_cat.dtype.names: # pragma: no cover - # This will fail if they try to make a parametric galaxy. - # Don't raise an exception here, since they might not care about that. - # But give them some guidance about the error they will get if they - # do try to make a parametric galaxy. - import warnings - warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. ' - 'Please run `galsim_download_cosmos -s %s` to re-download ' - 'the COSMOS catalog.'%(self.use_sample), GalSimWarning) - # NB. The pyfits FITS_Rec class has a bug where it makes a copy of the full # record array in each record (e.g. in getParametricRecord) and then doesn't # garbage collect it until the top-level FITS_Record goes out of scope. @@ -232,80 +223,67 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, self.param_cat = np.array(self.param_cat, copy=True) self.orig_index = np.arange(len(self.param_cat)) - mask = np.ones(len(self.orig_index), dtype=bool) + self._apply_exclusion(exclusion_level, min_hlr, max_hlr, min_flux, max_flux) + + def _apply_exclusion(self, exclusion_level, min_hlr=0, max_hlr=0, min_flux=0, max_flux=0): + from ._pyfits import pyfits + mask = np.ones(len(self.orig_index), dtype=bool) if exclusion_level in ['marginal', 'bad_stamp']: # First, read in what we need to impose selection criteria, if the appropriate # exclusion_level was chosen. - k = full_file_name.find('.fits') + + # This should work if the user passed in (or we defaulted to) the real galaxy + # catalog name: + selection_file_name = self.full_file_name.replace('.fits', '_selection.fits') try: - # This should work if the user passed in (or we defaulted to) the real galaxy - # catalog name: - selection_file_name = full_file_name[:k] + '_selection' + full_file_name[k:] + with pyfits.open(selection_file_name) as fits: + self.selection_cat = fits[1].data + except (IOError, OSError): + # There's one more option: full_file_name might be the parametric fit file, so + # we have to strip off the _fits.fits (instead of just the .fits) + selection_file_name = self.full_file_name.replace('_fits', '_selection') try: with pyfits.open(selection_file_name) as fits: self.selection_cat = fits[1].data - except (IOError, OSError): - # There's one more option: full_file_name might be the parametric fit file, so - # we have to strip off the _fits.fits (instead of just the .fits) - selection_file_name = full_file_name[:k-5] + '_selection' + full_file_name[k:] - with pyfits.open(selection_file_name) as fits: - self.selection_cat = fits[1].data - - - # At this point we've read in the catalog one way or another (otherwise we would - # have gotten tossed out of this part of the code to throw an IOError). So, we can - # proceed to select galaxies in a way that excludes suspect postage stamps (e.g., - # with deblending issues), suspect parametric model fits, or both of the above plus - # marginal ones. These two options for 'exclusion_level' involve placing cuts on - # the S/N of the object detection in the original postage stamp, and on issues with - # masking that can indicate deblending or detection failures. These cuts were used - # in GREAT3. In the case of the masking cut, in some cases there are messed up ones - # that have a 0 for self.selection_cat['peak_image_pixel_count']. To make sure we - # don't divide by zero (generating a RuntimeWarning), and still eliminate those, we - # will first set that column to 1.e-5. We choose a sample-dependent mask ratio cut, - # since this depends on the peak object flux, which will differ for the two samples - # (and we can't really cut on this for arbitrary user-defined samples). - if self.use_sample == "23.5": - cut_ratio = 0.2 - sn_limit = 20.0 - else: - cut_ratio = 0.8 - sn_limit = 12.0 - div_val = self.selection_cat['peak_image_pixel_count'] - div_val[div_val == 0.] = 1.e-5 - mask &= ( (self.selection_cat['sn_ellip_gauss'] >= sn_limit) & - ((self.selection_cat['min_mask_dist_pixels'] > 11.0) | - (self.selection_cat['average_mask_adjacent_pixel_count'] / \ - div_val < cut_ratio)) ) - except (IOError, OSError): - # We can't make any of the above cuts (or any later ones that depend on the - # selection catalog) because we couldn't find the selection catalog. Bummer. Warn - # the user, and move on. - self.selection_cat = None - import warnings - warnings.warn( - 'File with GalSim selection criteria not found. ' - 'Not all of the requested exclusions will be performed. ' - 'Run the program `galsim_download_cosmos -s %s` to get the ' - 'necessary selection file.'%(self.use_sample), GalSimWarning) + except (IOError, OSError): # pragma: no cover + raise OSError("File with GalSim selection criteria not found. " + "Run the program `galsim_download_cosmos -s %s` to get the " + "necessary selection file."%(self.use_sample)) + + # We proceed to select galaxies in a way that excludes suspect postage stamps (e.g., + # with deblending issues), suspect parametric model fits, or both of the above plus + # marginal ones. These two options for 'exclusion_level' involve placing cuts on + # the S/N of the object detection in the original postage stamp, and on issues with + # masking that can indicate deblending or detection failures. These cuts were used + # in GREAT3. In the case of the masking cut, in some cases there are messed up ones + # that have a 0 for self.selection_cat['peak_image_pixel_count']. To make sure we + # don't divide by zero (generating a RuntimeWarning), and still eliminate those, we + # will first set that column to 1.e-5. We choose a sample-dependent mask ratio cut, + # since this depends on the peak object flux, which will differ for the two samples + # (and we can't really cut on this for arbitrary user-defined samples). + if self.use_sample == "23.5": + cut_ratio = 0.2 + sn_limit = 20.0 + else: + cut_ratio = 0.8 + sn_limit = 12.0 + div_val = self.selection_cat['peak_image_pixel_count'] + div_val[div_val == 0.] = 1.e-5 + mask &= ( (self.selection_cat['sn_ellip_gauss'] >= sn_limit) & + ((self.selection_cat['min_mask_dist_pixels'] > 11.0) | + (self.selection_cat['average_mask_adjacent_pixel_count'] / \ + div_val < cut_ratio)) ) # Finally, impose a cut that the total flux in the postage stamp should be positive, # which excludes a tiny number of galaxies (of order 10 in each sample) with some sky # subtraction or deblending errors. Some of these are eliminated by other cuts when # using exclusion_level='marginal'. - if hasattr(self,'real_cat'): - if hasattr(self.real_cat, 'stamp_flux'): - mask &= self.real_cat.stamp_flux > 0 - else: - import warnings - warnings.warn( - 'This version of the COSMOS catalog does not have info about total flux ' - 'in the galaxy postage stamps. Exclusion of negative-flux stamps in ' - 'advance cannot be done. ' - 'Run the program `galsim_download_cosmos -s %s` to get the updated ' - 'catalog with this information precomputed.'%(self.use_sample), - GalSimWarning) + if self.real_cat is not None: + if not hasattr(self.real_cat, 'stamp_flux'): # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + mask &= self.real_cat.stamp_flux > 0 if exclusion_level in ['bad_fits', 'marginal']: # This 'exclusion_level' involves eliminating failed parametric fits (bad fit status @@ -330,7 +308,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # Some fit parameters can indicate a likely sky subtraction error: very high sersic n # AND abnormally large half-light radius (>1 arcsec). - if 'hlr' not in self.param_cat.dtype.names: # pragma: no cover + if 'hlr' not in self.param_cat.dtype.names: # This is the circularized HLR in arcsec, which we have to compute from the stored # parametric fits. hlr = cosmos_pix_scale * self.param_cat['sersicfit'][:,1] * \ @@ -343,41 +321,15 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # Major flux differences in the parametric model vs. the COSMOS catalog can indicate fit # issues, deblending problems, etc. - if self.selection_cat is not None: - mask &= ( np.abs(self.selection_cat['dmag']) < 0.8) + mask &= ( np.abs(self.selection_cat['dmag']) < 0.8) if min_hlr > 0. or max_hlr > 0. or min_flux > 0. or max_flux > 0.: if 'hlr' not in self.param_cat.dtype.names: # pragma: no cover - # Check if they have a new version of the selection catalog that has precomputed - # fluxes etc. If not, do the calculations, which include some approximations in - # getting the flux. - import warnings - warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. ' - 'Please run `galsim_download_cosmos -s %s` to re-download the ' - 'COSMOS catalog to get faster and more accurate selection.'%(self.use_sample), - GalSimWarning) - - sparams = self.param_cat['sersicfit'] - hlr_pix = sparams[:,1] - n = sparams[:,2] - q = sparams[:,3] - hlr = cosmos_pix_scale*hlr_pix*np.sqrt(q) - if min_flux > 0. or max_flux > 0.: - flux_hlr = sparams[:,0] - # The prefactor for n=4 is 3.607. For n=1, it is 1.901. - # It's not linear in these values, but for the sake of efficiency and the - # ability to work on the whole array at once, just linearly interpolate. - # This was improved as part of issue #693, for which part of the work involved - # updating the catalogs to include precomputed fluxes and radii. So as shown - # below, if that info is in the catalog we just use it directly instead of using - # this approximate calculation. - #prefactor = ( (n-1.)*3.607 + (4.-n)*1.901 ) / (4.-1.) - prefactor = ((3.607-1.901)/3.) * n + (4.*1.901 - 1.*3.607)/3. - flux = 2.0*np.pi*prefactor*(hlr**2)*flux_hlr/cosmos_pix_scale**2 - else: - hlr = self.param_cat['hlr'][:,0] # sersic half-light radius - flux = self.param_cat['flux'][:,0] + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + + hlr = self.param_cat['hlr'][:,0] # sersic half-light radius + flux = self.param_cat['flux'][:,0] if min_hlr > 0.: mask &= (hlr > min_hlr) @@ -394,10 +346,10 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, # We need this method because the config apparatus will use this via a Proxy, and they cannot # access attributes directly -- just call methods. So this is how we get nobjects there. def getNObjects(self) : return self.nobjects - + def getUseSample(self): return self.use_sample def getOrigIndex(self, index): return self.orig_index[index] - def getNTot(self) : return len(self.param_cat) + def __len__(self): return self.nobjects def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size=5, deep=False, sersic_prec=0.05, rng=None, n_random=None, gsparams=None): @@ -476,14 +428,20 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= @returns Either a GSObject or a ChromaticObject depending on the value of `chromatic`, or a list of them if `index` is an iterable. """ + return self._makeGalaxy(self, index, gal_type, chromatic, noise_pad_size, + deep, sersic_prec, rng, n_random, gsparams) + + @staticmethod + def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size=5, + deep=False, sersic_prec=0.05, rng=None, n_random=None, gsparams=None): from .random import BaseDeviate - if not self.use_real: + if not self.canMakeReal(): if gal_type is None: gal_type = 'parametric' elif gal_type != 'parametric': raise GalSimIncompatibleValuesError( "Only 'parametric' galaxy type is allowed when use_real == False", - gal_type=gal_type, use_real=self.use_real) + gal_type=gal_type, use_real=self.canMakeReal()) else: if gal_type is None: gal_type = 'real' @@ -491,10 +449,6 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= if gal_type not in ('real', 'parametric'): raise GalSimValueError("Invalid galaxy type %r", gal_type, ('real', 'parametric')) - # We'll set these up if and when we need them. - self._bandpass = None - self._sed = None - # Make rng if we will need it. if index is None or gal_type == 'real': if rng is None: @@ -522,33 +476,38 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= if gal_type == 'real': if chromatic: raise NotImplementedError("Cannot yet make real chromatic galaxies!") - gal_list = self._makeReal(indices, noise_pad_size, rng, gsparams) - else: - # If no pre-selection was done based on radius or flux, then we won't have checked - # whether we're using the old or new catalog (the latter of which has a lot of - # precomputations done). Just in case, let's check here, though it does seem like a bit - # of overkill to emit this warning each time. - if 'hlr' not in self.param_cat.dtype.names: # pragma: no cover - import warnings - warnings.warn( - 'You seem to have an old version of the COSMOS parameter file. ' - 'Please run `galsim_download_cosmos -s %s` to re-download the COSMOS ' - 'catalog and take advantage of pre-computation of many quantities..'%( - self.use_sample), GalSimWarning) + gal_list = [] + for idx in indices: + real_params = self.getRealParams(idx) + gal = RealGalaxy(real_params, noise_pad_size=noise_pad_size, rng=rng, + gsparams=gsparams) + gal_list.append(gal) - gal_list = self._makeParametric(indices, chromatic, sersic_prec, gsparams) + else: + if chromatic: + bandpass = self.getBandpass() + sed = self.getSED() + else: + bandpass = None + sed = None + gal_list = [] + for idx in indices: + record = self.getParametricRecord(idx) + gal = COSMOSCatalog._buildParametric(record, sersic_prec, gsparams, + chromatic, bandpass, sed) + gal_list.append(gal) # If trying to use the 23.5 sample and "fake" a deep sample, rescale the size and flux as # suggested in the GREAT3 handbook. if deep: - if self.use_sample == '23.5': + if self.getUseSample() == '23.5': # Rescale the flux to get a limiting mag of 25 in F814W when starting with a # limiting mag of 23.5. Make the galaxies a factor of 0.6 smaller and appropriately # fainter. flux_factor = 10.**(-0.4*1.5) size_factor = 0.6 gal_list = [ gal.dilate(size_factor) * flux_factor for gal in gal_list ] - elif self.use_sample == '25.2': + elif self.getUseSample() == '25.2': import warnings warnings.warn( 'Ignoring `deep` argument, because the sample being used already ' @@ -560,12 +519,12 @@ def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size= 'corresponds to a flux limit of F814W<23.5', GalSimWarning) # Store the orig_index as gal.index regardless of whether we have a RealGalaxy or not. - # It gets set by _makeReal, but not by _makeParametric. + # It gets set as part of making a real galaxy, but not by _buildParametric. # And if we are doing the deep scaling, then it gets messed up by that. # So just put it in here at the end to be sure. for gal, idx in zip(gal_list, indices): - gal.index = self.orig_index[idx] - if hasattr(gal, 'original'): gal.original.index = self.orig_index[idx] + gal.index = self.getOrigIndex(idx) + if hasattr(gal, 'original'): gal.original.index = gal.index if hasattr(index, '__iter__'): return gal_list @@ -591,7 +550,7 @@ def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False): if rng is None: rng = BaseDeviate() - if hasattr(self, 'real_cat') and hasattr(self.real_cat, 'weight'): + if hasattr(self.real_cat, 'weight'): use_weights = self.real_cat.weight[self.orig_index] else: import warnings @@ -617,49 +576,40 @@ def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False): else: return index[0] - def _makeReal(self, indices, noise_pad_size, rng, gsparams): - return [ RealGalaxy(self.real_cat, index=self.orig_index[i], - noise_pad_size=noise_pad_size, rng=rng, gsparams=gsparams) - for i in indices ] - - def _makeParametric(self, indices, chromatic, sersic_prec, gsparams): + def getBandpass(self): from .bandpass import Bandpass + # Defer making the Bandpass and reading in SEDs until we actually are going to use them. + # It's not a huge calculation, but the thin() call especially isn't trivial. + + if self._bandpass is None: + # We have to set an appropriate zeropoint. This is slightly complicated: The + # nominal COSMOS zeropoint for single-orbit depth (2000s of usable exposure time, + # across 4 dithered exposures) is supposedly 25.94. But the science images that we + # are using were normalized to count rate, not counts, meaning that an object with + # mag=25.94 has a count rate of 1 photon/sec, not 1 photon total. Since we've + # declared our flux normalization for the outputs to be appropriate for a 1s + # exposure, we use this zeropoint directly. + # This means that when drawing chromatic parametric galaxies, the outputs will be + # properly normalized in terms of counts. + zp = 25.94 + self._bandpass = Bandpass('ACS_wfc_F814W.dat', wave_type='nm').withZeropoint(zp) + return self._bandpass + + def getSED(self): from .sed import SED - if chromatic: - # Defer making the Bandpass and reading in SEDs until we actually are going to use them. - # It's not a huge calculation, but the thin() call especially isn't trivial. - if self._bandpass is None: - # We have to set an appropriate zeropoint. This is slightly complicated: The - # nominal COSMOS zeropoint for single-orbit depth (2000s of usable exposure time, - # across 4 dithered exposures) is supposedly 25.94. But the science images that we - # are using were normalized to count rate, not counts, meaning that an object with - # mag=25.94 has a count rate of 1 photon/sec, not 1 photon total. Since we've - # declared our flux normalization for the outputs to be appropriate for a 1s - # exposure, we use this zeropoint directly. - zp = 25.94 - self._bandpass = Bandpass('ACS_wfc_F814W.dat', wave_type='nm').withZeropoint(zp) - # This means that when drawing chromatic parametric galaxies, the outputs will be - # properly normalized in terms of counts. - - # Read in some SEDs. We are using some fairly truncated and thinned ones, because - # in any case the SED assignment here is somewhat arbitrary and should not be taken - # too seriously. - self._sed = [ - # bulge - SED('CWW_E_ext_more.sed', wave_type='Ang', flux_type='flambda'), - # disk - SED('CWW_Scd_ext_more.sed', wave_type='Ang', flux_type='flambda'), - # intermediate - SED('CWW_Sbc_ext_more.sed', wave_type='Ang', flux_type='flambda')] - - gal_list = [] - for index in indices: - record = self.getParametricRecord(index) - gal = self._buildParametric(record, sersic_prec, gsparams, - chromatic, self._bandpass, self._sed) - gal_list.append(gal) - - return gal_list + if self._sed is None: + # Read in some SEDs. We are using some fairly truncated and thinned ones, because + # in any case the SED assignment here is somewhat arbitrary and should not be taken + # too seriously. + self._sed = [ + # bulge + SED('CWW_E_ext_more.sed', wave_type='Ang', flux_type='flambda'), + # disk + SED('CWW_Scd_ext_more.sed', wave_type='Ang', flux_type='flambda'), + # intermediate + SED('CWW_Sbc_ext_more.sed', wave_type='Ang', flux_type='flambda') + ] + return self._sed @staticmethod def _round_sersic(n, sersic_prec): @@ -689,45 +639,12 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se bparams = record['bulgefit'] sparams = record['sersicfit'] if 'hlr' not in record: # pragma: no cover - # This code is here for backwards compatibility; if they have an old version of the - # catalog, then we have to do all calculations. - # - # Get the status flag for the fits. Entries 0 and 4 in 'fit_status' are relevant for - # bulgefit and sersicfit, respectively. - bstat = record['fit_status'][0] - sstat = record['fit_status'][4] - # Get the precomputed bulge-to-total flux ratio for the 2-component fits. - dvc_btt = record['fit_dvc_btt'] - # Get the precomputed median absolute deviation for the 1- and 2-component fits. These - # quantities are used to ascertain whether the 2-component fit is really justified, or - # if the 1-component Sersic fit is sufficient to describe the galaxy light profile. - bmad = record['fit_mad_b'] - smad = record['fit_mad_s'] - - # First decide if we can / should use bulgefit, otherwise sersicfit. This decision - # process depends on: the status flags for the fits, the bulge-to-total ratios (if near - # 0 or 1, just use single component fits), the sizes for the bulge and disk (if <=0 then - # use single component fits), the axis ratios for the bulge and disk (if <0.051 then use - # single component fits), and a comparison of the median absolute deviations to see - # which is better. The reason for the 0.051 cutoff is that the fits were bound at 0.05 - # as a minimum, so anything below 0.051 generally means that the fitter hit the boundary - # for the 2-component fits, typically meaning that we don't have enough information to - # make reliable 2-component fits. - use_bulgefit = True - if ( bstat < 1 or bstat > 4 or dvc_btt < 0.1 or dvc_btt > 0.9 or - np.isnan(dvc_btt) or bparams[9] <= 0 or - bparams[1] <= 0 or bparams[11] < 0.051 or bparams[3] < 0.051 or - smad < bmad ): - use_bulgefit = False - # Then check if sersicfit is viable; if not, this galaxy is a total failure. - # Note that we can avoid including these in the catalog in the first place by using - # `exclusion_level=bad_fits` or `exclusion_level=marginal` when making the catalog. - if sstat < 1 or sstat > 4 or sparams[1] <= 0 or sparams[0] <= 0: - raise GalSimError("Cannot make parametric model for this galaxy!") - else: - use_bulgefit = record['use_bulgefit'] - if not use_bulgefit and not record['viable_sersic']: - raise GalSimError("Cannot make parametric model for this galaxy!") + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + + use_bulgefit = record['use_bulgefit'] + if not use_bulgefit and not record['viable_sersic']: # pragma: no cover + raise GalSimError("Cannot make parametric model for this galaxy!") if use_bulgefit: # Bulge parameters: @@ -737,27 +654,14 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se bulge_beta = bparams[15]*radians disk_q = bparams[3] disk_beta = bparams[7]*radians - if 'hlr' not in record: # pragma: no cover - # If we're supposed to use the 2-component fits, get all the parameters. - # We have to convert from the stored half-light radius along the major axis, to an - # azimuthally averaged one (multiplying by sqrt(bulge_q)). We also have to convert - # to our native units of arcsec, from units of COSMOS pixels. - bulge_hlr = cosmos_pix_scale*np.sqrt(bulge_q)*bparams[9] - # The stored quantity is the surface brightness at the half-light radius. We have - # to convert to total flux within an n=4 surface brightness profile. - bulge_flux = 2.0*np.pi*3.607*(bulge_hlr**2)*bparams[8]/cosmos_pix_scale**2 - # Disk parameters, defined analogously: - disk_hlr = cosmos_pix_scale*np.sqrt(disk_q)*bparams[1] - disk_flux = 2.0*np.pi*1.901*(disk_hlr**2)*bparams[0]/cosmos_pix_scale**2 - else: - bulge_hlr = record['hlr'][1] - bulge_flux = record['flux'][1] - disk_hlr = record['hlr'][2] - disk_flux = record['flux'][2] + bulge_hlr = record['hlr'][1] + bulge_flux = record['flux'][1] + disk_hlr = record['hlr'][2] + disk_flux = record['flux'][2] # Make sure the bulge-to-total flux ratio is not nonsense. bfrac = bulge_flux/(bulge_flux+disk_flux) - if bfrac < 0 or bfrac > 1 or np.isnan(bfrac): + if bfrac < 0 or bfrac > 1 or np.isnan(bfrac): # pragma: no cover raise GalSimError("Cannot make parametric model for this galaxy") # Then combine the two components of the galaxy. @@ -781,9 +685,9 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se gsparams=gsparams) # Apply shears for intrinsic shape. - if bulge_q < 1.: + if bulge_q < 1.: # pragma: no branch bulge = bulge.shear(q=bulge_q, beta=bulge_beta) - if disk_q < 1.: + if disk_q < 1.: # pragma: no branch disk = disk.shear(q=disk_q, beta=disk_beta) gal = bulge + disk @@ -803,19 +707,8 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se gal_n = COSMOSCatalog._round_sersic(gal_n, sersic_prec) gal_q = sparams[3] gal_beta = sparams[7]*radians - - if 'hlr' not in record: # pragma: no cover - gal_hlr = cosmos_pix_scale*np.sqrt(gal_q)*sparams[1] - # Below is the calculation of the full Sersic n-dependent quantity that goes into - # the conversion from surface brightness to flux, which here we're calling - # 'prefactor'. In the n=4 and n=1 cases above, this was precomputed, but here we - # have to calculate for each value of n. - tmp_ser = Sersic(gal_n, half_light_radius=gal_hlr, gsparams=gsparams) - gal_flux = sparams[0] / tmp_ser.xValue(0,gal_hlr) / cosmos_pix_scale**2 - else: - gal_hlr = record['hlr'][0] - gal_flux = record['flux'][0] - + gal_hlr = record['hlr'][0] + gal_flux = record['flux'][0] if chromatic: gal = Sersic(gal_n, flux=1., half_light_radius=gal_hlr, gsparams=gsparams) @@ -832,7 +725,7 @@ def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, se gal = Sersic(gal_n, flux=gal_flux, half_light_radius=gal_hlr, gsparams=gsparams) # Apply shears for intrinsic shape. - if gal_q < 1.: + if gal_q < 1.: # pragma: no branch gal = gal.shear(q=gal_q, beta=gal_beta) return gal @@ -849,7 +742,7 @@ def getRealParams(self, index): def getParametricRecord(self, index): """Get the parametric record for a given index""" - # Used by _makeSingleGalaxy to circumvent pickling the result. + # Used by _makeGalaxy to circumvent pickling the result. record = self.param_cat[self.orig_index[index]] # Convert to a dict, since on some systems, the numpy record doesn't seem to # pickle correctly. @@ -860,76 +753,10 @@ def canMakeReal(self): """Is it permissible to call makeGalaxy with gal_type='real'?""" return self.use_real - @staticmethod - def _makeSingleGalaxy(cosmos_catalog, index, gal_type, noise_pad_size=5, deep=False, - rng=None, sersic_prec=0.05, gsparams=None): - from .random import BaseDeviate - # A static function that mimics the functionality of COSMOSCatalog.makeGalaxy() - # for single index and chromatic=False. - # The only point of this class is to circumvent some pickling issues when using - # config objects with type : COSMOSGalaxy. It's a staticmethod, which means it - # cannot use any self attributes. Just methods. (Which also means we can use it - # through a proxy COSMOSCatalog object, which we need for the config layer.) - - if not cosmos_catalog.canMakeReal(): - if gal_type is None: - gal_type = 'parametric' - elif gal_type != 'parametric': - raise GalSimIncompatibleValuesError( - "Only 'parametric' galaxy type is allowed when use_real == False", - gal_type=gal_type, use_real=False) - - if gal_type not in ('real', 'parametric'): - raise GalSimValueError("Invalid galaxy type", gal_type, ('real', 'parametric')) - - if gal_type == 'real' and rng is None: - rng = BaseDeviate() - - if gal_type == 'real': - real_params = cosmos_catalog.getRealParams(index) - gal = RealGalaxy(real_params, noise_pad_size=noise_pad_size, rng=rng, gsparams=gsparams) - else: - record = cosmos_catalog.getParametricRecord(index) - gal = COSMOSCatalog._buildParametric(record, sersic_prec, gsparams, chromatic=False) - - # If trying to use the 23.5 sample and "fake" a deep sample, rescale the size and flux as - # suggested in the GREAT3 handbook. - if deep: - if self.use_sample == '23.5': - # Rescale the flux to get a limiting mag of 25 in F814W when starting with a - # limiting mag of 23.5. Make the galaxies a factor of 0.6 smaller and appropriately - # fainter. - flux_factor = 10.**(-0.4*1.5) - size_factor = 0.6 - gal = gal.dilate(size_factor) * flux_factor - else: - import warnings - warnings.warn( - 'Ignoring `deep` argument, because the sample being used already ' - 'corresponds to a flux limit of F814W<25.2', GalSimWarning) - - # Store the orig_index as gal.index, since the above RealGalaxy initialization just sets it - # as 0. Plus, it isn't set at all if we make a parametric galaxy. And if we are doing the - # deep scaling, then it gets messed up by that. If we have done some transformations, and - # are also doing later transformation, it will take the `original` attribute that is already - # there. So having `index` doesn't help, and we also need `original.index`. - gal.index = cosmos_catalog.getOrigIndex(index) - if hasattr(gal, 'original'): - gal.original.index = cosmos_catalog.getOrigIndex(index) - - return gal - - # Since this is a function, not a class, need to use an unconventional location for defining - # these config parameters. Also, I thought it would make sense to attach them to the - # _makeSingleGalaxy method. But that doesn't work, since it is technically a staticmethod - # object, not a normal function. So we attach these to makeGalaxy instead. - makeGalaxy._req_params = {} - makeGalaxy._opt_params = { "index" : int, - "gal_type" : str, - "noise_pad_size" : float, - "deep" : bool, - "sersic_prec": float, - "n_random": int - } - makeGalaxy._single_params = [] - makeGalaxy._takes_rng = True + def __eq__(self, other): + return (isinstance(other, COSMOSCatalog) and + self.use_real == other.use_real and + self.use_sample == other.use_sample and + self.real_cat == other.real_cat and + np.array_equal(self.param_cat, other.param_cat) and + np.array_equal(self.orig_index, other.orig_index)) diff --git a/galsim/utilities.py b/galsim/utilities.py index 06e854e22d2..163a725feaa 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -1309,9 +1309,9 @@ def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): # Sanity check the requested number of random indices. # Note: we do not require that the type be an int, as long as the value is consistent with # an integer value (i.e., it could be a float 1.0 or 1). - if not n-int(n) == 0 or n < 1: + if n != int(n) or n < 1: raise GalSimValueError("n must be an integer >= 1.", n) - if not n_choices-int(n_choices) == 0 or n_choices < 1: + if n_choices != int(n_choices) or n_choices < 1: raise GalSimValueError("n_choices must be an integer >= 1.", n_choices) # Sanity check the input weight. diff --git a/tests/test_scene.py b/tests/test_scene.py index ca8681fb976..5a3495aec3c 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -41,7 +41,33 @@ def test_cosmos_basic(): # Initialize one that doesn't exclude failures. It should be >= the previous one in length. cat2 = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath, exclusion_level='none') - assert cat2.nobjects>=cat.nobjects + assert cat2.nobjects >= cat.nobjects + assert len(cat2) == cat2.nobjects == cat2.getNTot() == 100 + assert len(cat) == cat.nobjects < cat.getNTot() + + # Check other exclusion levels: + cat3 = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', + dir=datapath, exclusion_level='bad_stamp') + assert len(cat3) == 97 + cat4 = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', + dir=datapath, exclusion_level='bad_fits') + assert len(cat4) == 100 # no bad fits in the example file as it happens. + cat5 = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', + dir=datapath, exclusion_level='marginal') + assert len(cat5) == 96 # this is actually the default, so == cat + assert cat == cat5 + + # Check the 25.2 exclusions. We don't have a 25.2 catalog available in Travis runs, so + # mock up the example catalog as though it were 25.2 + # Also check the min/max hlr and flux options. + cat2.use_sample = '25.2' + hlr = cat2.param_cat['hlr'][:,0] + flux = cat2.param_cat['flux'][:,0] + print("Full range of hlr = ", np.min(hlr), np.max(hlr)) + print("Full range of flux = ", np.min(flux), np.max(flux)) + cat2._apply_exclusion('marginal', min_hlr=0.2, max_hlr=2, min_flux=50, max_flux=5000) + print("Size of catalog with hlr and flux exclusions == ",len(cat2)) + assert len(cat2) == 47 # Check for reasonable exceptions when initializing. # Can't find data (wrong directory). @@ -49,44 +75,71 @@ def test_cosmos_basic(): galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits') # Try making galaxies - gal_real = cat2.makeGalaxy(index=0,gal_type='real',chromatic=False) - if not isinstance(gal_real, galsim.RealGalaxy): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return an instance of " - "'galsim.RealGalaxy'") + gal_real = cat.makeGalaxy(index=0,gal_type='real',chromatic=False) + assert isinstance(gal_real, galsim.RealGalaxy) gal_param = cat.makeGalaxy(index=10,gal_type='parametric',chromatic=True) - if not isinstance(gal_param, galsim.ChromaticObject): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return an instance of " - "'galsim.ChromaticObject' for parametric galaxies") + assert isinstance(gal_param, galsim.ChromaticObject) + + # Second time through, don't make the bandpass. + bp = cat._bandpass + sed = cat._sed + assert bp is not None + gal_param2 = cat.makeGalaxy(index=13, gal_type='parametric', chromatic=True) + assert isinstance(gal_param2, galsim.ChromaticObject) + assert gal_param != gal_param2 + assert cat._bandpass is bp # Not just ==. "is" means the same object. + assert cat._sed is sed + + # So far, we've made a bulge+disk and a disky Sersic. + # Do two more to run through two more paths in the code. + gal_param3 = cat.makeGalaxy(index=50, gal_type='parametric', chromatic=True) + gal_param4 = cat.makeGalaxy(index=67, gal_type='parametric', chromatic=True) gal_real_list = cat.makeGalaxy(index=[3,6],gal_type='real',chromatic=False) for gal_real in gal_real_list: - if not isinstance(gal_real, galsim.RealGalaxy): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return a list of instances " - "of 'galsim.RealGalaxy'") + assert isinstance(gal_real, galsim.RealGalaxy) gal_param_list = cat.makeGalaxy(index=[4,7],gal_type='parametric',chromatic=False) for gal_param in gal_param_list: - if not isinstance(gal_param, galsim.GSObject): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return a list of instances " - "of 'galsim.GSObect'") + assert isinstance(gal_param, galsim.GSObject) # Check for parametric catalog - cat_param = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example_fits.fits', + # Can give either the regular name or the _fits name. + cat_param = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath, use_real=False) + cat_param2 = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example_fits.fits', + dir=datapath, use_real=False) + assert cat_param2 == cat_param # Try making galaxies gal = cat_param.makeGalaxy(index=1) - if not isinstance(gal, galsim.GSObject): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return an instance of " - "'galsim.GSObject when loaded from a fits file.") + assert isinstance(gal, galsim.GSObject) gal_list = cat_param.makeGalaxy(index=[2,3]) for gal in gal_list: - if not isinstance(gal, galsim.GSObject): - raise TypeError("COSMOS Catalog makeGalaxy routine does not return a list of instances " - "of 'galsim.GSObject when loaded from a fits file.") + assert isinstance(gal, galsim.GSObject) + + # Check sersic_prec option. + sersic0 = cat_param.makeGalaxy(index=59, sersic_prec=0) + np.testing.assert_almost_equal(sersic0.original.n, 1.14494567108) + sersic1 = cat_param.makeGalaxy(index=59, sersic_prec=0.05) # The default. + np.testing.assert_almost_equal(sersic1.original.n, 1.15) + sersic2 = cat_param.makeGalaxy(index=59, sersic_prec=0.1) + np.testing.assert_almost_equal(sersic2.original.n, 1.1) + sersic3 = cat_param.makeGalaxy(index=59, sersic_prec=0.5) + np.testing.assert_almost_equal(sersic3.original.n, 1.0) + + assert_raises(TypeError, galsim.COSMOSCatalog, 'real_galaxy_catalog_23.5_example.fits', + dir=datapath, sample='23.5') + assert_raises(ValueError, galsim.COSMOSCatalog, sample='invalid') + + assert_raises(ValueError, cat_param.makeGalaxy, gal_type='real') + assert_raises(ValueError, cat_param.makeGalaxy, gal_type='invalid') + assert_raises(ValueError, cat.makeGalaxy, gal_type='invalid') + assert_raises(TypeError, cat_param.makeGalaxy, rng='invalid') + assert_raises(NotImplementedError, cat.makeGalaxy, gal_type='real', chromatic=True) @timer def test_cosmos_fluxnorm(): @@ -134,6 +187,9 @@ def test_cosmos_fluxnorm(): assert hasattr(gal1_param.shear(g1=0.05).original, 'index'), \ 'Bulge+disk galaxy does not retain index information after transformation' + assert_raises(ValueError, galsim.COSMOSCatalog, 'real_galaxy_catalog_23.5_example.fits', + dir=datapath, exclusion_level='invalid') + @timer def test_cosmos_random(): """Check the random object functionality of the COSMOS catalog.""" @@ -148,8 +204,8 @@ def test_cosmos_random(): dir=datapath) cat_param = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath, use_real=False) - assert hasattr(cat, 'real_cat') - assert not hasattr(cat_param, 'real_cat') + assert cat_param.real_cat is None + assert cat.real_cat is not None # Check for exception handling if bad inputs given for the random functionality. assert_raises(ValueError, cat.selectRandomIndex, 0) @@ -171,99 +227,147 @@ def test_cosmos_random(): # to probabilistically select galaxies. np.testing.assert_almost_equal(np.mean(wtrand), wavg_weight_val,3, err_msg='Average weight for random sample is wrong') - # From here on we need to suppress some warnings that come from calling cat_param. It doesn't - # have weights, so it does unweighted selection, which emits a warning. We know about this and - # don't want to spit out the warning each time, so suppress it. - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + + # The example catalog doesn't have weights, so it does unweighted selection, which emits a + # warning. We know about this and want to ignore it here. + with assert_warns(galsim.GalSimWarning): randind = cat_param.selectRandomIndex(30000, rng=galsim.BaseDeviate(1234)) - wtrand = cat.real_cat.weight[cat.orig_index[randind]] / \ - np.max(cat.real_cat.weight[cat.orig_index]) - # The average value of wtrand should be avg_weight_val, since we did not do a weighted - # selection. - np.testing.assert_almost_equal(np.mean(wtrand), avg_weight_val,3, - err_msg='Average weight for random sample is wrong') - - # Check for consistency of randoms with same random seed. Do this both for the weighted and the - # unweighted calculation. - # Check for inconsistency of randoms with different random seed, or same seed but without/with - # weighting. - rng1 = galsim.BaseDeviate(1234) - rng2 = galsim.BaseDeviate(1234) - ind1 = cat.selectRandomIndex(10, rng=rng1) - ind2 = cat.selectRandomIndex(10, rng=rng2) - np.testing.assert_array_equal(ind1,ind2, - err_msg='Different random indices selected with same seed!') + wtrand = cat.real_cat.weight[cat.orig_index[randind]] / \ + np.max(cat.real_cat.weight[cat.orig_index]) + # The average value of wtrand should be avg_weight_val, since we did not do a weighted + # selection. + np.testing.assert_almost_equal(np.mean(wtrand), avg_weight_val,3, + err_msg='Average weight for random sample is wrong') + + # Check for consistency of randoms with same random seed. Do this both for the weighted and the + # unweighted calculation. + # Check for inconsistency of randoms with different random seed, or same seed but without/with + # weighting. + rng1 = galsim.BaseDeviate(1234) + rng2 = galsim.BaseDeviate(1234) + ind1 = cat.selectRandomIndex(10, rng=rng1) + ind2 = cat.selectRandomIndex(10, rng=rng2) + np.testing.assert_array_equal(ind1,ind2, + err_msg='Different random indices selected with same seed!') + with assert_warns(galsim.GalSimWarning): ind1p = cat_param.selectRandomIndex(10, rng=rng1) + with assert_warns(galsim.GalSimWarning): ind2p = cat_param.selectRandomIndex(10, rng=rng2) - np.testing.assert_array_equal(ind1p,ind2p, - err_msg='Different random indices selected with same seed!') - rng3 = galsim.BaseDeviate(5678) - ind3 = cat.selectRandomIndex(10, rng=rng3) + np.testing.assert_array_equal(ind1p,ind2p, + err_msg='Different random indices selected with same seed!') + rng3 = galsim.BaseDeviate(5678) + ind3 = cat.selectRandomIndex(10, rng=rng3) + with assert_warns(galsim.GalSimWarning): ind3p = cat_param.selectRandomIndex(10) # initialize RNG based on time - assert_raises(AssertionError, np.testing.assert_array_equal, ind1, ind1p) - assert_raises(AssertionError, np.testing.assert_array_equal, ind1, ind3) - assert_raises(AssertionError, np.testing.assert_array_equal, ind1p, ind3p) - - # Finally, make sure that directly calling selectRandomIndex() gives the same random ones as - # makeGalaxy(). We'll do one real object because they are slower, and multiple parametric (just - # to make sure that the multi-object selection works consistently). - use_seed = 567 - obj = cat.makeGalaxy(rng=galsim.BaseDeviate(use_seed)) - ind = cat.selectRandomIndex(1, rng=galsim.BaseDeviate(use_seed)) - obj_2 = cat.makeGalaxy(ind) - # Note: for real galaxies we cannot require that obj==obj_2, just that obj.index==obj_2.index. - # That's because we want to make sure the same galaxy is being randomly selected, but we cannot - # require that noise padding be the same, given the inconsistency in how the BaseDeviates are - # used in the above cases. - assert obj.index==obj_2.index,'makeGalaxy selects random objects inconsistently' - - n_random = 3 + assert_raises(AssertionError, np.testing.assert_array_equal, ind1, ind1p) + assert_raises(AssertionError, np.testing.assert_array_equal, ind1, ind3) + assert_raises(AssertionError, np.testing.assert_array_equal, ind1p, ind3p) + + # Finally, make sure that directly calling selectRandomIndex() gives the same random ones as + # makeGalaxy(). We'll do one real object because they are slower, and multiple parametric (just + # to make sure that the multi-object selection works consistently). + use_seed = 567 + obj = cat.makeGalaxy(rng=galsim.BaseDeviate(use_seed)) + ind = cat.selectRandomIndex(1, rng=galsim.BaseDeviate(use_seed)) + obj_2 = cat.makeGalaxy(ind) + # Note: for real galaxies we cannot require that obj==obj_2, just that obj.index==obj_2.index. + # That's because we want to make sure the same galaxy is being randomly selected, but we cannot + # require that noise padding be the same, given the inconsistency in how the BaseDeviates are + # used in the above cases. + assert obj.index==obj_2.index,'makeGalaxy selects random objects inconsistently' + + n_random = 3 + with assert_warns(galsim.GalSimWarning): objs = cat_param.makeGalaxy(rng=galsim.BaseDeviate(use_seed), n_random=n_random) + with assert_warns(galsim.GalSimWarning): inds = cat_param.selectRandomIndex(n_random, rng=galsim.BaseDeviate(use_seed)) - objs_2 = cat_param.makeGalaxy(inds) - for i in range(n_random): - # With parametric objects there is no noise padding, so we can require completely identical - # GSObjects (not just equal indices). - assert objs[i]==objs_2[i],'makeGalaxy selects random objects inconsistently' - - # Finally, check for consistency with random object selected from RealGalaxyCatalog. For this - # case, we need to make another COSMOSCatalog that does not flag the bad objects. - use_seed=31415 - cat = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', - dir=datapath, exclusion_level='none') - rgc = galsim.RealGalaxyCatalog(file_name='real_galaxy_catalog_23.5_example.fits', - dir=datapath) - ind_cc = cat.selectRandomIndex(1, rng=galsim.BaseDeviate(use_seed)) - foo = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(use_seed)) - ind_rgc = foo.index - assert ind_cc==ind_rgc,\ - 'Different weighted random index selected from COSMOSCatalog and RealGalaxyCatalog' - # Also check for the unweighted case. Just remove that info from the catalogs and redo the - # test. - del cat.real_cat - del rgc.weight + objs_2 = cat_param.makeGalaxy(inds) + for i in range(n_random): + # With parametric objects there is no noise padding, so we can require completely identical + # GSObjects (not just equal indices). + assert objs[i]==objs_2[i],'makeGalaxy selects random objects inconsistently' + + # Finally, check for consistency with random object selected from RealGalaxyCatalog. For this + # case, we need to make another COSMOSCatalog that does not flag the bad objects. + use_seed=31415 + cat = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', + dir=datapath, exclusion_level='none') + rgc = galsim.RealGalaxyCatalog(file_name='real_galaxy_catalog_23.5_example.fits', + dir=datapath) + ind_cc = cat.selectRandomIndex(1, rng=galsim.BaseDeviate(use_seed)) + foo = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(use_seed)) + ind_rgc = foo.index + assert ind_cc==ind_rgc,\ + 'Different weighted random index selected from COSMOSCatalog and RealGalaxyCatalog' + + # Also check for the unweighted case. Just remove that info from the catalogs and redo the + # test. + cat.real_cat = None + del rgc.weight + with assert_warns(galsim.GalSimWarning): ind_cc = cat.selectRandomIndex(1, rng=galsim.BaseDeviate(use_seed)) - foo = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(use_seed)) - ind_rgc = foo.index - assert ind_cc==ind_rgc,\ - 'Different unweighted random index selected from COSMOSCatalog and RealGalaxyCatalog' - - # Check that setting _n_rng_calls properly tracks the RNG calls for n_random=1 and >1. - test_seed = 123456 - ud = galsim.UniformDeviate(test_seed) + foo = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(use_seed)) + ind_rgc = foo.index + assert ind_cc==ind_rgc,\ + 'Different unweighted random index selected from COSMOSCatalog and RealGalaxyCatalog' + + # Check that setting _n_rng_calls properly tracks the RNG calls for n_random=1 and >1. + test_seed = 123456 + ud = galsim.UniformDeviate(test_seed) + with assert_warns(galsim.GalSimWarning): obj, n_rng_calls = cat.selectRandomIndex(1, rng=ud, _n_rng_calls=True) - ud2 = galsim.UniformDeviate(test_seed) - ud2.discard(n_rng_calls) - assert ud()==ud2(), '_n_rng_calls kwarg did not give proper tracking of RNG calls' - ud = galsim.UniformDeviate(test_seed) + ud2 = galsim.UniformDeviate(test_seed) + ud2.discard(n_rng_calls) + assert ud()==ud2(), '_n_rng_calls kwarg did not give proper tracking of RNG calls' + ud = galsim.UniformDeviate(test_seed) + with assert_warns(galsim.GalSimWarning): obj, n_rng_calls = cat.selectRandomIndex(17, rng=ud, _n_rng_calls=True) - ud2 = galsim.UniformDeviate(test_seed) - ud2.discard(n_rng_calls) - assert ud()==ud2(), '_n_rng_calls kwarg did not give proper tracking of RNG calls' + ud2 = galsim.UniformDeviate(test_seed) + ud2.discard(n_rng_calls) + assert ud()==ud2(), '_n_rng_calls kwarg did not give proper tracking of RNG calls' + +@timer +def test_cosmos_deep(): + """Test the deep option of makeGalaxy""" + + cat = galsim.COSMOSCatalog(file_name='real_galaxy_catalog_23.5_example.fits', dir=datapath) + + # Pick a random galaxy + # Turn off noise padding to make the comparisons more reliable. + gal_shallow = cat.makeGalaxy(index=17, gal_type='real', noise_pad_size=0) + print('gal_shallow = ',gal_shallow) + shallow_flux = gal_shallow.flux + shallow_hlr = gal_shallow.calculateHLR() + print('flux = ',shallow_flux) + print('hlr = ',shallow_hlr) + + gal_deep = cat.makeGalaxy(index=17, gal_type='real', deep=True, noise_pad_size=0) + print('gal_deep = ',gal_deep) + deep_flux = gal_deep.flux + deep_hlr = gal_deep.calculateHLR() + print('flux = ',deep_flux) + print('hlr = ',deep_hlr) + + # Deep galaxy is fainter and smaller. + np.testing.assert_almost_equal(deep_flux / shallow_flux, 10.**(-0.6)) + np.testing.assert_almost_equal(deep_hlr / shallow_hlr, 0.6) + + # With samples other than 23.5, it raises a warning and doesn't do any scaling. + cat.use_sample = '25.2' + with assert_warns(galsim.GalSimWarning): + gal_not_deep = cat.makeGalaxy(index=17, gal_type='real', deep=True, noise_pad_size=0) + assert gal_not_deep.flux == shallow_flux + assert gal_not_deep.calculateHLR() == shallow_hlr + + cat.use_sample = 'user_defined' + with assert_warns(galsim.GalSimWarning): + gal_not_deep = cat.makeGalaxy(index=17, gal_type='real', deep=True, noise_pad_size=0) + assert gal_not_deep.flux == shallow_flux + assert gal_not_deep.calculateHLR() == shallow_hlr + if __name__ == "__main__": test_cosmos_basic() test_cosmos_fluxnorm() test_cosmos_random() + test_cosmos_deep() diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 55f3575d840..56648bb21e3 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -791,51 +791,76 @@ def test_rand_with_replacement(): """Test routine to select random indices with replacement.""" # Most aspects of this routine get tested when it's used by COSMOSCatalog. We just check some # of the exception-handling here. + + # Invalid rng + with assert_raises(TypeError): + galsim.utilities.rand_with_replacement( + n=2, n_choices=10, rng='foo') + + # Invalid n with assert_raises(ValueError): galsim.utilities.rand_with_replacement( n=1.5, n_choices=10, rng=galsim.BaseDeviate(1234)) - with assert_raises(TypeError): + with assert_raises(ValueError): galsim.utilities.rand_with_replacement( - n=2, n_choices=10, rng='foo') + n=0, n_choices=10, rng=galsim.BaseDeviate(1234)) + with assert_raises(ValueError): + galsim.utilities.rand_with_replacement( + n=-2, n_choices=10, rng=galsim.BaseDeviate(1234)) + + # Invalid n_choices with assert_raises(ValueError): galsim.utilities.rand_with_replacement( n=2, n_choices=10.5, rng=galsim.BaseDeviate(1234)) with assert_raises(ValueError): galsim.utilities.rand_with_replacement( - n=2, n_choices=-11, rng=galsim.BaseDeviate(1234)) + n=2, n_choices=0, rng=galsim.BaseDeviate(1234)) with assert_raises(ValueError): galsim.utilities.rand_with_replacement( - n=-2, n_choices=11, rng=galsim.BaseDeviate(1234)) + n=2, n_choices=-11, rng=galsim.BaseDeviate(1234)) + # Negative weights tmp_weights = np.arange(10).astype(float)-3 with assert_raises(ValueError): galsim.utilities.rand_with_replacement( n=2, n_choices=10, rng=galsim.BaseDeviate(1234), weight=tmp_weights) + # NaN weights tmp_weights[0] = np.nan with assert_raises(ValueError): galsim.utilities.rand_with_replacement( n=2, n_choices=10, rng=galsim.BaseDeviate(1234), weight=tmp_weights) + # inf weights tmp_weights[0] = np.inf with assert_raises(ValueError): galsim.utilities.rand_with_replacement( n=2, n_choices=10, rng=galsim.BaseDeviate(1234), weight=tmp_weights) + # Wrong length for weights + with assert_raises(ValueError): + galsim.utilities.rand_with_replacement( + n=2, n_choices=10, rng=galsim.BaseDeviate(1234), weight=tmp_weights[:4]) + # Make sure results come out the same whether we use _n_rng_calls or not. - result_1 = galsim.utilities.rand_with_replacement(n=10, n_choices=100, - rng=galsim.BaseDeviate(314159)) - result_2, _ = galsim.utilities.rand_with_replacement(n=10, n_choices=100, - rng=galsim.BaseDeviate(314159), - _n_rng_calls=True) + rng1 = galsim.BaseDeviate(314159) + rng2 = galsim.BaseDeviate(314159) + rng3 = galsim.BaseDeviate(314159) + result_1 = galsim.utilities.rand_with_replacement(n=10, n_choices=100, rng=rng1) + result_2, n_rng = galsim.utilities.rand_with_replacement(n=10, n_choices=100, rng=rng2, + _n_rng_calls=True) assert np.all(result_1==result_2),"Using _n_rng_calls results in different random numbers" + rng3.discard(n_rng) + assert rng1.raw() == rng2.raw() == rng3.raw() + + # Repeat with weights weight = np.zeros(100) galsim.UniformDeviate(1234).generate(weight) - result_1 = galsim.utilities.rand_with_replacement( - n=10, n_choices=100, rng=galsim.BaseDeviate(314159), weight=weight) + result_1 = galsim.utilities.rand_with_replacement(10, 100, rng1, weight=weight) assert not np.all(result_1==result_2),"Weights did not have an effect" - result_2, _ = galsim.utilities.rand_with_replacement( - n=10, n_choices=100, rng=galsim.BaseDeviate(314159), - weight=weight, _n_rng_calls=True) + result_2, n_rng = galsim.utilities.rand_with_replacement(10, 100, rng2, weight=weight, + _n_rng_calls=True) assert np.all(result_1==result_2),"Using _n_rng_calls results in different random numbers" + rng3.discard(n_rng) + assert rng1.raw() == rng2.raw() == rng3.raw() @timer def test_position_type_promotion(): From a61b39ea0cc287b99922082875c1f5adc708cb5e Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 1 May 2018 10:50:14 -0400 Subject: [PATCH 59/96] Increase test coverage of phase_psf (#755) --- galsim/phase_psf.py | 99 +++++++++++++++++++-------------------- galsim/utilities.py | 12 ++--- galsim/zernike.py | 13 +---- tests/test_optics.py | 21 ++++++++- tests/test_phase_psf.py | 83 ++++++++++++++++++++++++++++---- tests/test_second_kick.py | 16 +++++++ tests/test_zernike.py | 4 ++ 7 files changed, 168 insertions(+), 80 deletions(-) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index d3452b08a2c..7dbfa49f5a6 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -79,7 +79,8 @@ from .wcs import PixelScale from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property -from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError +from .errors import GalSimWarning class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -217,8 +218,10 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, self.diam = diam # Always need to explicitly specify an aperture diameter. self._obscuration = obscuration # We store this, even though it's not always used. - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) + if diam <= 0.: + raise GalSimRangeError("Invalid diam.", diam, 0.) if obscuration < 0. or obscuration >= 1.: raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) @@ -286,7 +289,7 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, else: # Use geometric parameters. if pupil_plane_scale is not None: # Check input scale and warn if looks suspicious. - if pupil_plane_scale > good_pupil_scale: # pragma: no cover + if pupil_plane_scale > good_pupil_scale: import warnings ratio = good_pupil_scale / pupil_plane_scale warnings.warn("Input pupil_plane_scale may be too large for good sampling.\n" @@ -297,7 +300,7 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, pupil_plane_scale = good_pupil_scale if pupil_plane_size is not None: # Check input size and warn if looks suspicious - if pupil_plane_size < good_pupil_size: # pragma: no cover + if pupil_plane_size < good_pupil_size: import warnings ratio = good_pupil_size / pupil_plane_size warnings.warn("Input pupil_plane_size may be too small for good focal-plane" @@ -311,16 +314,6 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, nstruts, strut_thick, strut_angle, pupil_plane_scale, pupil_plane_size) - # Check FFT size - if self._gsparams is not None: - maximum_fft_size = self._gsparams.maximum_fft_size - else: - maximum_fft_size = GSParams().maximum_fft_size - if self.npix > maximum_fft_size: - raise GalSimError("Created pupil plane array that is too large, {0} " - "If you can handle the large FFT, you may update " - "gsparams.maximum_fft_size".format(self.npix)) - def _generate_pupil_plane(self, circular_pupil, nstruts, strut_thick, strut_angle, pupil_plane_scale, pupil_plane_size): """ Create an array of illuminated pixels parameterically. @@ -329,6 +322,13 @@ def _generate_pupil_plane(self, circular_pupil, nstruts, strut_thick, strut_angl # Fudge a little to prevent good_fft_size() from turning 512.0001 into 768. ratio *= (1.0 - 1.0/2**14) self.npix = Image.good_fft_size(int(np.ceil(ratio))) + + # Check FFT size + if self.npix > self._gsparams.maximum_fft_size: + raise GalSimError("Created pupil plane array that is too large, {0} " + "If you can handle the large FFT, you may update " + "gsparams.maximum_fft_size".format(self.npix)) + self.pupil_plane_size = pupil_plane_size # Shrink scale such that size = scale * npix exactly. self.pupil_plane_scale = pupil_plane_size / self.npix @@ -389,6 +389,12 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good pp_arr = pupil_plane_im.array self.npix = pp_arr.shape[0] + # Check FFT size + if self.npix > self._gsparams.maximum_fft_size: + raise GalSimError("Loaded pupil plane array that is too large, {0} " + "If you can handle the large FFT, you may update " + "gsparams.maximum_fft_size".format(self.npix)) + # Sanity checks if pupil_plane_im.array.shape[0] != pupil_plane_im.array.shape[1]: raise GalSimValueError("Input pupil_plane_im must be square.", @@ -426,7 +432,7 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good self.pupil_plane_size = self.pupil_plane_scale * self.npix # Check sampling interval and warn if it's not good enough. - if self.pupil_plane_scale > good_pupil_scale: # pragma: no cover + if self.pupil_plane_scale > good_pupil_scale: import warnings ratio = self.pupil_plane_scale / good_pupil_scale warnings.warn("Input pupil plane image may not be sampled well enough!\n" @@ -531,39 +537,37 @@ def illuminated(self): """ return self._illuminated - @property + @lazy_property def rho(self): """ Unit-disk normalized pupil plane coordinate as a complex number: (x, y) => x + 1j * y. """ - if not hasattr(self, '_rho') or self._rho is None: - u = np.fft.fftshift(np.fft.fftfreq(self.npix, self.diam/self.pupil_plane_size/2.0)) - u, v = np.meshgrid(u, u) - self._rho = u + 1j * v - return self._rho + f1 = np.fft.fftfreq(self.npix, 1./self.pupil_plane_size) + f2 = np.fft.fftfreq(self.npix, self.diam/self.pupil_plane_size/2.0) + u = np.fft.fftshift(np.fft.fftfreq(self.npix, self.diam/self.pupil_plane_size/2.0)) + u, v = np.meshgrid(u, u) + return u + 1j * v + + @lazy_property + def _uv(self): + u = np.fft.fftshift(np.fft.fftfreq(self.npix, 1./self.pupil_plane_size)) + u, v = np.meshgrid(u, u) + return u, v @property def u(self): """Pupil horizontal coordinate array in meters.""" - if not hasattr(self, '_u'): - u = np.fft.fftshift(np.fft.fftfreq(self.npix, 1./self.pupil_plane_size)) - self._u, self._v = np.meshgrid(u, u) - return self._u + return self._uv[0] @property def v(self): """Pupil vertical coordinate array in meters.""" - if not hasattr(self, '_v'): - u = np.fft.fftshift(np.fft.fftfreq(self.npix, 1./self.pupil_plane_size)) - self._u, self._v = np.meshgrid(u, u) - return self._v + return self._uv[1] - @property + @lazy_property def rsqr(self): """Pupil radius squared array in meters squared.""" - if not hasattr(self, '_rsqr'): - self._rsqr = self.u**2 + self.v**2 - return self._rsqr + return self.u**2 + self.v**2 @property def obscuration(self): @@ -591,6 +595,9 @@ def __getstate__(self): # - Implies relation between aperture grid and real-space grid: # dL = lambda/theta # L = lambda/dtheta + # + # MJ: Of these four, only _sky_scale is still used. The rest are left here for informational + # purposes, but nothing actually calls them. def _getStepK(self, lam, scale_unit=arcsec): """Return the Fourier grid spacing for this aperture at given wavelength. @@ -657,20 +664,15 @@ def __init__(self, *layers): if len(layers) == 1: # First check if layers[0] is a PhaseScreenList, so we avoid nesting. if isinstance(layers[0], PhaseScreenList): - layers = layers[0]._layers + self._layers = layers[0]._layers else: # Next, see if layers[0] is iterable. E.g., to catch generator expressions. try: - layers = list(layers[0]) + self._layers = list(layers[0]) except TypeError: - # If that fails, check if layers[0] is a bare PhaseScreen. Should probably - # make an ABC for this (use __subclasshook__?), but for now, just check - # AtmosphericScreen and OpticalScreen. - if isinstance(layers[0], (AtmosphericScreen, OpticalScreen)): - layers = [layers[0]] - # else, layers is either empty or a tuple of PhaseScreens and so responds appropriately - # to list() below. - self._layers = list(layers) + self._layers = list(layers) + else: + self._layers = list(layers) self._update_attrs() self._pending = [] # Pending PSFs to calculate upon first drawImage. @@ -684,7 +686,7 @@ def __getitem__(self, index): return cls(self._layers[index]) elif isinstance(index, numbers.Integral): return self._layers[index] - else: # pragma: no cover + else: msg = "{cls.__name__} indices must be integers" raise TypeError(msg.format(cls=cls)) @@ -1144,7 +1146,7 @@ def __init__(self, screen_list, lam, t0=0.0, exptime=0.0, time_step=0.025, flux= if isinstance(scale_unit, str): scale_unit = AngleUnit.from_name(scale_unit) self.scale_unit = scale_unit - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) self.scale = aper._sky_scale(self.lam, self.scale_unit) self._force_stepk = _force_stepk @@ -1688,17 +1690,14 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., def gsparams=gsparams) self.obscuration = obscuration else: - if hasattr(aper, '_obscuration'): - self.obscuration = aper._obscuration - else: - self.obscuration = 0.0 + self.obscuration = aper.obscuration # Save for pickling self._lam = float(lam) self._flux = float(flux) self._interpolant = interpolant self._scale_unit = scale_unit - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) self._suppress_warning = suppress_warning self._geometric_shooting = geometric_shooting self._aper = aper diff --git a/galsim/utilities.py b/galsim/utilities.py index 163a725feaa..a8ef79cdec5 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -103,9 +103,6 @@ def rotate_xy(x, y, theta): @return the rotated coordinates `(x_rot,y_rot)`. """ - from .angle import Angle - if not isinstance(theta, Angle): - raise TypeError("Input rotation angle theta must be a galsim.Angle instance.") sint, cost = theta.sincos() x_rot = x * cost - y * sint y_rot = x * sint + y * cost @@ -1067,8 +1064,11 @@ def structure_function(image): where the x and r on the RHS are 2D vectors, but the |r| on the LHS is just a scalar length. - @param image Image containing random field realization. The `.scale` attribute here *is* used - in the calculation. If it's `None`, then the code will use 1.0 for the scale. + The image must have its `scale` attribute defined. It will be used in the calculations to + set the scale of the radial distances. + + @param image Image containing random field realization. + @returns A python callable mapping a separation length r to the estimate of the structure function D(r). """ @@ -1076,8 +1076,6 @@ def structure_function(image): array = image.array nx, ny = array.shape scale = image.scale - if scale is None: - scale = 1.0 # The structure function can be derived from the correlation function B(r) as: # D(r) = 2 * [B(0) - B(r)] diff --git a/galsim/zernike.py b/galsim/zernike.py index 1bc34487203..01b870b2e8e 100644 --- a/galsim/zernike.py +++ b/galsim/zernike.py @@ -22,7 +22,7 @@ import numpy as np from .utilities import LRU_Cache, binomial, horner2d, nCr, lazy_property -from .errors import GalSimValueError +from .errors import GalSimValueError, GalSimRangeError # Some utilities for working with Zernike polynomials @@ -432,17 +432,6 @@ def __init__(self, coef, R_outer=1.0, R_inner=0.0): self.R_outer = float(R_outer) self.R_inner = float(R_inner) - # _coef_array property only exists to support the deprecated OpticalPSF.coef_array attribute. - # It can be deleted in version 2.0. - @lazy_property - def _coef_array(self): - arr = _noll_coef_array(len(self.coef)-1, self.R_inner/self.R_outer).dot(self.coef[1:]) - - if self.R_outer != 1.0: - shape = arr.shape - arr /= self.R_outer**np.sum(np.mgrid[0:2*shape[0]:2, 0:shape[1]], axis=0) - return arr - @lazy_property def _coef_array_xy(self): arr = _noll_coef_array_xy(len(self.coef)-1, self.R_inner/self.R_outer).dot(self.coef[1:]) diff --git a/tests/test_optics.py b/tests/test_optics.py index 7d8e5ff383c..46c287e61fa 100644 --- a/tests/test_optics.py +++ b/tests/test_optics.py @@ -408,7 +408,7 @@ def test_OpticalPSF_pupil_plane(): .format(pp_file)) im = galsim.Image(ref_psf._psf.aper.illuminated.astype(float)) im.scale = ref_psf._psf.aper.pupil_plane_scale - print('pupil_plane image has scale = ',pp_scale) + print('pupil_plane image has scale = ',im.scale) im.write(os.path.join(imgdir, pp_file)) pp_scale = im.scale print('pupil_plane image has scale = ',pp_scale) @@ -418,7 +418,7 @@ def test_OpticalPSF_pupil_plane(): # there is a specific scale for the pupil plane image. But see the last test below where # we do use lam, diam separately with the input image. im.wcs = None - # This implies that the lam_over_diam value + # This implies that the lam_over_diam value is valid. test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, oversampling=pp_oversampling, pupil_plane_im=im, pad_factor=pp_pad_factor) @@ -441,6 +441,23 @@ def test_OpticalPSF_pupil_plane(): do_pickle(test_psf, lambda x: x.drawImage(nx=20, ny=20, scale=0.07, method='no_pixel')) do_pickle(test_psf) + # Make a smaller pupil plane image to test the pickling of this, even without slow tests. + with assert_warns(galsim.GalSimWarning): + alt_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, + oversampling=1., pupil_plane_im=im.bin(4,4), + pad_factor=1.) + do_pickle(alt_psf) + + assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, pupil_plane_im='pp_file') + assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, pupil_plane_im=im, + pupil_plane_scale=pp_scale) + assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, + pupil_plane_im=im.view(scale=pp_scale)) + assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, + pupil_plane_im=galsim.Image(im.array[:-2,:])) + assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, + pupil_plane_im=galsim.Image(im.array[:-1,:-1])) + # It is supposed to be able to figure this out even if we *don't* tell it the pad factor. So # make sure that it still works even if we don't tell it that value. test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im, diff --git a/tests/test_phase_psf.py b/tests/test_phase_psf.py index b3675535d4f..7ef5dc55dda 100644 --- a/tests/test_phase_psf.py +++ b/tests/test_phase_psf.py @@ -34,20 +34,61 @@ def test_aperture(): """Test various ways to construct Apertures.""" # Simple tests for constructing and pickling Apertures. - aper1 = galsim.Aperture(diam=1.0) + aper1 = galsim.Aperture(diam=1.7) im = galsim.fits.read(os.path.join(imgdir, pp_file)) - aper2 = galsim.Aperture(diam=1.0, pupil_plane_im=im) - aper3 = galsim.Aperture(diam=1.0, nstruts=4) + aper2 = galsim.Aperture(diam=1.7, pupil_plane_im=im) + aper3 = galsim.Aperture(diam=1.7, nstruts=4) do_pickle(aper1) do_pickle(aper2) do_pickle(aper3) # Automatically created Aperture should match one created via OpticalScreen - aper1 = galsim.Aperture(diam=1.0) - aper2 = galsim.Aperture(diam=1.0, lam=500, screen_list=[galsim.OpticalScreen(diam=1.0)]) + aper1 = galsim.Aperture(diam=1.7) + aper2 = galsim.Aperture(diam=1.7, lam=500, screen_list=[galsim.OpticalScreen(diam=1.7)]) err_str = ("Aperture created implicitly using Airy does not match Aperture created using " "OpticalScreen.") assert aper1 == aper2, err_str + assert_raises(ValueError, galsim.Aperture, 1.7, obscuration=-0.3) + assert_raises(ValueError, galsim.Aperture, 1.7, obscuration=1.1) + assert_raises(ValueError, galsim.Aperture, -1.7) + assert_raises(ValueError, galsim.Aperture, 0) + + assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, circular_pupil=False) + assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, nstruts=2) + assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_thick=0.01) + assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_angle=5*galsim.degrees) + assert_raises(ValueError, galsim.Aperture, 1.7, pupil_plane_im=im, strut_angle=5*galsim.degrees) + assert_raises(ValueError, galsim.Aperture, 1.7, screen_list=[galsim.OpticalScreen(diam=1)]) + + # rho is a convenience property that can be useful when debugging, but isn't used in the + # main code base. + np.testing.assert_almost_equal(aper1.rho, aper1.u * 2./1.7 + 1j * aper1.v * 2./1.7) + + # Some other functions that aren't used by anything anymore, but were useful in development. + for lam in [300, 550, 1200]: + stepk = aper1._getStepK(lam=lam) + maxk = aper1._getMaxK(lam=lam) + scale = aper1._sky_scale(lam=lam) + size = aper1._sky_size(lam=lam) + np.testing.assert_almost_equal(stepk, 2.*np.pi/size) + np.testing.assert_almost_equal(maxk, np.pi/scale) + + # If the constructed pupil plane would be too large, raise an error + assert_raises(galsim.GalSimError, galsim.Aperture, 1.7, pupil_plane_scale=1.e-4) + + # Similar if the given image is too large. + # Here, we change gsparams.maximum_fft_size, rather than build a really large image to load. + assert_raises(galsim.GalSimError, galsim.Aperture, 1.7, pupil_plane_im=im, + gsparams=galsim.GSParams(maximum_fft_size=64)) + + # Other choices just give warnings + with assert_warns(galsim.GalSimWarning): + galsim.Aperture(diam=1.7, pupil_plane_size=3, pupil_plane_scale=0.03) + + im.wcs = None # Otherwise get an error. + with assert_warns(galsim.GalSimWarning): + galsim.Aperture(diam=1.7, pupil_plane_im=im, pupil_plane_scale=0.03) + @timer def test_atm_screen_size(): @@ -166,7 +207,7 @@ def test_phase_screen_list(): do_pickle(atm, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__ - atm2 = galsim.PhaseScreenList(atm[:-1]) # Refers to first n-1 screens + atm2 = atm[:-1] # Refers to first n-1 screens assert atm != atm2 # Append a different screen to the end of atm2 atm2.append(ar2) @@ -176,6 +217,11 @@ def test_phase_screen_list(): atm2.append(atm[-1]) assert atm == atm2 + with assert_raises(TypeError): + atm['invalid'] + with assert_raises(IndexError): + atm[3] + # Test building from empty PhaseScreenList atm3 = galsim.PhaseScreenList() atm3.extend(atm2) @@ -252,6 +298,19 @@ def test_phase_screen_list(): np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent") + # Check errors in u,v,t shapes. + assert_raises(ValueError, ar1.wavefront, aper.u, aper.v[:-1,:-1]) + assert_raises(ValueError, ar1.wavefront, aper.u[:-1,:-1], aper.v) + assert_raises(ValueError, ar1.wavefront, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) + assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v[:-1,:-1]) + assert_raises(ValueError, ar1.wavefront_gradient, aper.u[:-1,:-1], aper.v) + assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) + + assert_raises(ValueError, ar3.wavefront, aper.u, aper.v[:-1,:-1]) + assert_raises(ValueError, ar3.wavefront, aper.u[:-1,:-1], aper.v) + assert_raises(ValueError, ar3.wavefront_gradient, aper.u, aper.v[:-1,:-1]) + assert_raises(ValueError, ar3.wavefront_gradient, aper.u[:-1,:-1], aper.v) + @timer def test_frozen_flow(): @@ -264,9 +323,7 @@ def test_frozen_flow(): alt = x/1000 # -> 0.00005 km; silly example, but yields exact results... screen = galsim.AtmosphericScreen(1.0, dx, alt, vx=vx, rng=rng) - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with assert_warns(galsim.GalSimWarning): aper = galsim.Aperture(diam=1, pupil_plane_size=20., pupil_plane_scale=20./dx) wf0 = screen.wavefront(aper.u, aper.v, None, theta0) dwdu0, dwdv0 = screen.wavefront_gradient(aper.u, aper.v, t=screen._time) @@ -391,6 +448,14 @@ def test_scale_unit(): opt_psf2 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit='arcsec') assert opt_psf1 == opt_psf2, "scale unit did not parse as string" + assert_raises(ValueError, galsim.OpticalPSF, lam=500.0, diam=1.0, scale_unit='invalid') + assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, scale_unit='invalid') + # Check a few other construction errors now too. + assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, scale_unit='arcmin') + assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=34.*galsim.degrees) + assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=(34, 5)) + assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, exptime=-1) + @timer def test_stepk_maxk(): diff --git a/tests/test_second_kick.py b/tests/test_second_kick.py index 8fc9b6d961e..fa2e7e409ee 100644 --- a/tests/test_second_kick.py +++ b/tests/test_second_kick.py @@ -119,11 +119,27 @@ def test_limiting_cases(): rtol=1e-3, atol=1e-4) + # Normally, one wouldn't use SecondKick.xValue, since it does a real-space convolution, + # so it's slow. But we do allow it, so test it here. + import time + t0 = time.time() + xv_2k = sk.xValue(0,0) + print("xValue(0,0) = ",xv_2k) + t1 = time.time() + # The VonKarman * Airy xValue is much slower still, so don't do that. + # Instead compare it to the 'sb' image. + xv_image = limiting_case.drawImage(nx=1,ny=1,method='sb',scale=0.1)(1,1) + print('from image ',xv_image) + t2 = time.time() + print('t = ',t1-t0, t2-t1) + np.testing.assert_almost_equal(xv_2k, xv_image, decimal=3) + # kcrit=inf sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=np.inf) limiting_case = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration) for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]: + print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real) np.testing.assert_allclose( sk.kValue(0, k).real, limiting_case.kValue(0, k).real, diff --git a/tests/test_zernike.py b/tests/test_zernike.py index 0ff59780282..3ef5c809123 100644 --- a/tests/test_zernike.py +++ b/tests/test_zernike.py @@ -94,6 +94,10 @@ def test_Zernike_orthonormality(): do_pickle(Z1) do_pickle(Z1, lambda z: tuple(z.evalCartesian(x, y))) + with assert_raises(ValueError): + Z1 = galsim.zernike.Zernike([0]*4 + [0.1]*7, R_outer=R_inner, R_inner=R_outer) + val1 = Z1.evalCartesian(x, y) + @timer def test_annular_Zernike_limit(): From b89bf1b083d084de49c742cd15050585001c4d70 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Tue, 1 May 2018 19:28:38 -0400 Subject: [PATCH 60/96] Increase test coverage of correlatednoise (#755) --- galsim/correlatednoise.py | 183 ++++++++++++++-------------------- tests/test_correlatednoise.py | 54 +++++++++- 2 files changed, 124 insertions(+), 113 deletions(-) diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 1329c8dc37c..3da2d8c0aaa 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -25,6 +25,7 @@ from .image import Image from .random import BaseDeviate from .gsobject import GSObject +from .gsparams import GSParams from . import utilities from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimUndefinedBoundsError from .errors import GalSimIncompatibleValuesError, GalSimWarning @@ -83,9 +84,6 @@ def __init__(self, rng, gsobject, wcs): if rng is not None and not isinstance(rng, BaseDeviate): raise TypeError( "Supplied rng argument not a galsim.BaseDeviate or derived class instance.") - if not isinstance(gsobject, GSObject): - raise TypeError( - "Supplied gsobject argument not a galsim.GSObject or derived class instance.") if rng is None: rng = BaseDeviate() self._rng = rng @@ -94,18 +92,18 @@ def __init__(self, rng, gsobject, wcs): self.wcs = wcs # When applying normal or whitening noise to an image, we normally do calculations. - # If _profile_for_stored is profile, then it means that we can use the stored values in - # _rootps_store, _rootps_whitening_store, and/or _rootps_symmetrizing_store and avoid having + # If _profile_for_cached is profile, then it means that we can use the stored values in + # _rootps_cache, _rootps_whitening_cache, and/or _rootps_symmetrizing_cache and avoid having # to redo the calculations. - # So for now, we start out with _profile_for_stored = None, and _rootps_store, - # _rootps_whitening_store, _rootps_symmetrizing_store empty. - self._profile_for_stored = None - self._rootps_store = [] - self._rootps_whitening_store = [] - self._rootps_symmetrizing_store = [] + # So for now, we start out with _profile_for_cached = None, and _rootps_cache, + # _rootps_whitening_cache, _rootps_symmetrizing_cache empty. + self._profile_for_cache = None + self._rootps_cache = {} + self._rootps_whitening_cache = {} + self._rootps_symmetrizing_cache = {} # Also set up the cache for a stored value of the variance, needed for efficiency once the # noise field can get convolved with other GSObjects making is_analytic_x False - self._variance_stored = None + self._variance_cached = None @property def rng(self): return self._rng @@ -159,6 +157,17 @@ def __eq__(self, other): return repr(self) == repr(other) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(repr(self)) + def _clear_cache(self): + """Check if the profile has changed and clear caches if appropriate. + """ + if self._profile_for_cache is not self._profile: + self._rootps_cache.clear() + self._rootps_whitening_cache.clear() + self._rootps_symmetrizing_cache.clear() + self._variance_cached = None + # Set profile_for_cache for next time. + self._profile_for_cache = self._profile + def applyTo(self, image): """Apply this correlated Gaussian random noise field to an input Image. @@ -220,12 +229,7 @@ def applyTo(self, image): # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. - if self._profile_for_stored is not self._profile: - self._rootps_store = [] - self._rootps_whitening_store = [] - self._variance_stored = None - # Set profile_for_stored for next time. - self._profile_for_stored = self._profile + self._clear_cache() if image.wcs is None: wcs = self.wcs @@ -242,10 +246,6 @@ def applyTo(self, image): image.array[:,:] += noise_array return image - def applyToView(self, image_view): - raise GalSimError( - "CorrelatedNoise can only be applied to a regular Image, not an ImageView") - def whitenImage(self, image): """Apply noise designed to whiten correlated Gaussian random noise in an input Image. @@ -317,12 +317,7 @@ def whitenImage(self, image): # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. - if self._profile_for_stored is not self._profile: - self._rootps_store = [] - self._rootps_whitening_store = [] - self._variance_stored = None - # Set profile_for_stored for next time. - self._profile_for_stored = self._profile + self._clear_cache() if image.wcs is None: wcs = self.wcs @@ -410,11 +405,7 @@ def symmetrizeImage(self, image, order=4): # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. Note that this cache is not the same as the one used for # whitening. - if self._profile_for_stored is not self._profile: - self._rootps_store = [] - self._rootps_symmetrizing_store = [] - # Set profile_for_stored for next time. - self._profile_for_stored = self._profile + self._clear_cache() if image.wcs is None: wcs = self.wcs @@ -485,9 +476,6 @@ def rotate(self, theta): @returns a new CorrelatedNoise object with the specified rotation. """ - from .angle import Angle - if not isinstance(theta, Angle): - raise TypeError("Input theta should be an Angle") return _BaseCorrelatedNoise(self.rng, self._profile.rotate(theta), self.wcs) def shear(self, *args, **kwargs): @@ -531,21 +519,17 @@ def getVariance(self): else: # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. - if self._profile_for_stored is not self._profile: - self._rootps_store = [] - self._rootps_whitening_store = [] - self._variance_stored = None - # Set profile_for_stored for next time. - self._profile_for_stored = self._profile + self._clear_cache() + # Then use cached version or rebuild if necessary - if self._variance_stored is not None: - variance = self._variance_stored + if self._variance_cached is not None: + variance = self._variance_cached else: imtmp = Image(1, 1, dtype=float) # GalSim internals handle this correctly w/out folding self.drawImage(imtmp, scale=1.) variance = imtmp(1, 1) - self._variance_stored = variance # Store variance for next time + self._variance_cached = variance # Store variance for next time return variance def withVariance(self, variance): @@ -557,6 +541,8 @@ def withVariance(self, variance): @returns a CorrelatedNoise object with the new variance. """ + if variance <= 0.: + raise GalSimValueError("variance must be > 0 in withVariance", variance) variance_ratio = variance / self.getVariance() return self * variance_ratio @@ -702,19 +688,16 @@ def _get_update_rootps(self, shape, wcs): """Internal utility function for querying the `rootps` cache, used by applyTo(), whitenImage(), and symmetrizeImage() methods. """ - # First check whether we can just use a stored power spectrum (no drawing necessary if so) - use_stored = False # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) half_shape = (shape[0], shape[1] // 2 + 1) - for rootps_array, saved_wcs in self._rootps_store: - if rootps_array.shape == half_shape and wcs == saved_wcs: - use_stored = True - rootps = rootps_array - break + key = (half_shape, wcs) + + # Use the cached value if possible. + rootps = self._rootps_cache.get(key, None) # If not, draw the correlation function to the desired size and resolution, then DFT to # generate the required array of the square root of the power spectrum - if use_stored is False: + if rootps is None: # Draw this correlation function into an array. If this is not done at the same wcs as # the original image from which the CF derives, even if the image is rotated, then this # step requires interpolation and the newcf (used to generate the PS below) is thus @@ -724,9 +707,9 @@ def _get_update_rootps(self, shape, wcs): # Since we just drew it, save the variance value for posterity. var = newcf(newcf.bounds.center) - self._variance_stored = var + self._variance_cached = var - if var <= 0.: + if var <= 0.: # pragma: no cover This should be impossible... raise GalSimError("CorrelatedNoise found to have negative variance.") # Then calculate the sqrt(PS) that will be used to generate the actual noise. First do @@ -751,8 +734,8 @@ def _get_update_rootps(self, shape, wcs): # For now we just take the sqrt(abs(PS)): rootps = np.sqrt(np.abs(ps)) - # Then add this and the relevant wcs to the _rootps_store for later use - self._rootps_store.append((rootps, newcf.wcs)) + # Save this in the cache + self._rootps_cache[key] = rootps return rootps @@ -762,23 +745,20 @@ def _get_update_rootps_whitening(self, shape, wcs, headroom=1.05): @returns rootps_whitening, variance """ - # First check whether we can just use a stored whitening power spectrum - use_stored = False # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) half_shape = (shape[0], shape[1] // 2 + 1) - for rootps_whitening_array, saved_wcs, var in self._rootps_whitening_store: - if rootps_whitening_array.shape == half_shape and wcs == saved_wcs: - use_stored = True - rootps_whitening = rootps_whitening_array - variance = var - break + + key = (half_shape, wcs) + + # Use the cached values if possible. + rootps_whitening, variance = self._rootps_whitening_cache.get(key, (None,None)) # If not, calculate the whitening power spectrum as (almost) the smallest power spectrum # that when added to rootps**2 gives a flat resultant power that is nowhere negative. # Note that rootps = sqrt(power spectrum), and this procedure therefore works since power # spectra add (rather like variances). The resulting power spectrum will be all positive # (and thus physical). - if use_stored is False: + if rootps_whitening is None: rootps = self._get_update_rootps(shape, wcs) ps_whitening = -rootps * rootps @@ -790,8 +770,8 @@ def _get_update_rootps_whitening(self, shape, wcs, headroom=1.05): # element we could use any as the PS should be flat. variance = rootps[0, 0]**2 + ps_whitening[0, 0] - # Then add all this and the relevant wcs to the _rootps_whitening_store - self._rootps_whitening_store.append((rootps_whitening, wcs, variance)) + # Then add all this and the relevant wcs to the _rootps_whitening_cache + self._rootps_whitening_cache[key] = (rootps_whitening, variance) return rootps_whitening, variance @@ -801,28 +781,20 @@ def _get_update_rootps_symmetrizing(self, shape, wcs, order, headroom=1.02): @returns rootps_symmetrizing, variance """ - # First check whether we can just use a stored symmetrizing power spectrum. In addition for - # the considerations for use of cached observations for noise whitening, we need the - # requested order of the symmetry to be the same as the stored one. - use_stored = False # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) half_shape = (shape[0], shape[1] // 2 + 1) - for ( - rootps_symmetrizing_array, saved_wcs, var, saved_order - ) in self._rootps_symmetrizing_store: - if (rootps_symmetrizing_array.shape == half_shape and saved_order == order and - wcs == saved_wcs): - use_stored = True - rootps_symmetrizing = rootps_symmetrizing_array - variance = var - break + + key = (half_shape, wcs, order) + + # Use the cached values if possible. + rootps_symmetrizing, variance = self._rootps_symmetrizing_cache.get(key, (None,None)) # If not, calculate the symmetrizing power spectrum as (almost) the smallest power spectrum # that when added to rootps**2 gives a power that has N-fold symmetry, where `N=order`. # Note that rootps = sqrt(power spectrum), and this procedure therefore works since power # spectra add (rather like variances). The resulting power spectrum will be all positive # (and thus physical). - if use_stored is False: + if rootps_symmetrizing is None: rootps = self._get_update_rootps(shape, wcs) ps_actual = rootps * rootps @@ -836,10 +808,12 @@ def _get_update_rootps_symmetrizing(self, shape, wcs, order, headroom=1.02): # be generated with the rootps_symmetrizing. # Here, unlike in _get_update_rootps_whitening, the final power spectrum is not flat, so # we have to take the mean power instead of just using the [0, 0] element. + # Note that the mean of the power spectrum (fourier space) is the zero lag value in + # real space, which is the desired variance. variance = np.mean(rootps**2 + ps_symmetrizing) - # Then add all this and the relevant wcs to the _rootps_symmetrizing_store - self._rootps_symmetrizing_store.append((rootps_symmetrizing, wcs, variance, order)) + # Then add all this and the relevant wcs to the _rootps_symmetrizing_cache + self._rootps_symmetrizing_cache[key] = (rootps_symmetrizing, variance) return rootps_symmetrizing, variance @@ -928,15 +902,11 @@ def _generate_noise_from_rootps(rng, shape, rootps): @returns a NumPy array (contiguous) of the requested shape, filled with the noise field. """ from .random import GaussianDeviate - # Sanity check on requested shape versus that of rootps - if len(shape) != 2 or (shape[0], shape[1]//2+1) != rootps.shape: - raise GalSimValueError("Requested shape does not match that of the supplied rootps", shape) - # Quickest to create Gaussian rng each time needed, so do that here... - gd = GaussianDeviate( - rng, sigma=np.sqrt(.5 * shape[0] * shape[1])) # Note sigma scaling: 1/sqrt(2) needed so - # <|gaussvec|**2> = product(shape); shape - # needed because of the asymmetry in the - # 1/N^2 division in the NumPy FFT/iFFT + # Quickest to create Gaussian rng each time needed, so do that here... + # Note sigma scaling: 1/sqrt(2) needed so <|gaussvec|**2> = product(shape) + # shape needed because of the asymmetry in the 1/N^2 division in the NumPy FFT/iFFT + gd = GaussianDeviate(rng, sigma=np.sqrt(.5 * shape[0] * shape[1])) + # Fill a couple of arrays with this noise gvec_real = utilities.rand_arr((shape[0], shape[1]//2+1), gd) gvec_imag = utilities.rand_arr((shape[0], shape[1]//2+1), gd) @@ -1218,10 +1188,10 @@ def __init__(self, image, rng=None, scale=None, wcs=None, x_interpolant=None, if scale is not None: raise GalSimIncompatibleValuesError("Cannot provide both wcs and scale", scale=scale, wcs=scale) - if not wcs.isUniform(): - raise GalSimValueError("Cannot provide non-uniform wcs", wcs) if not isinstance(wcs, BaseWCS): raise TypeError("wcs must be a BaseWCS instance") + if not wcs.isUniform(): + raise GalSimValueError("Cannot provide non-uniform wcs", wcs) cf_image.wcs = wcs elif scale is not None: cf_image.scale = scale @@ -1247,10 +1217,12 @@ def __init__(self, image, rng=None, scale=None, wcs=None, x_interpolant=None, _BaseCorrelatedNoise.__init__(self, rng, cf_object, cf_image.wcs) if store_rootps: - # If it corresponds to the CF above, store useful data as a (rootps, wcs) tuple for - # efficient later use: - self._profile_for_stored = self._profile - self._rootps_store.append((np.sqrt(ps_array), cf_image.wcs)) + # If it corresponds to the CF above, store in the cache + self._profile_for_cached = self._profile + shape = ps_array.shape + half_shape = (shape[0], shape[1] // 2 + 1) + key = (half_shape, cf_image.wcs) + self._rootps_cache[key] = np.sqrt(ps_array) self._image = image @@ -1400,15 +1372,8 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i raise OSError("The file %r does not exist."%(file_name)) try: cfimage = fits.read(file_name) - except KeyboardInterrupt: - raise - except: # pragma: no cover - # Give a vaguely helpful warning, then raise the original exception for extra diagnostics - import warnings - warnings.warn( - "Function getCOSMOSNoise() unable to read FITS image from %r."(file_name), - GalSimWarning) - raise + except (IOError, OSError, AttributeError, TypeError): + raise OSError("Unable to read COSMOSNoise file %s"%(file_name)) # Then check for negative variance before doing anything time consuming if variance < 0: @@ -1494,7 +1459,7 @@ def __init__(self, variance, rng=None, scale=None, wcs=None, gsparams=None): # Save the things that won't get saved by the base class, for use in repr. self.variance = variance - self._gsparams = gsparams + self._gsparams = GSParams.check(gsparams) # Need variance == xvalue(0,0) after autoconvolution # So the Pixel needs to have an amplitude of sigma at (0,0) diff --git a/tests/test_correlatednoise.py b/tests/test_correlatednoise.py index 5f7ac92ed66..76cc97ed211 100644 --- a/tests/test_correlatednoise.py +++ b/tests/test_correlatednoise.py @@ -100,6 +100,14 @@ def test_uncorrelated_noise_zero_lag(): do_pickle(ucn) do_pickle(cn) + assert_raises(TypeError, galsim.UncorrelatedNoise) + assert_raises(ValueError, galsim.UncorrelatedNoise, variance = -1.0) + assert_raises(TypeError, galsim.UncorrelatedNoise, 1, scale=1, wcs=galsim.PixelScale(3)) + assert_raises(TypeError, galsim.UncorrelatedNoise, 1, wcs=1) + assert_raises(ValueError, galsim.UncorrelatedNoise, 1, + wcs=galsim.FitsWCS('fits_files/tpv.fits')) + assert_raises(TypeError, galsim.UncorrelatedNoise, 1, rng=10) + @timer def test_uncorrelated_noise_nonzero_lag(): @@ -506,7 +514,18 @@ def test_output_generation_basic(): cn.drawImage(refim, scale=.18) # Generate a large image containing noise according to this function outimage = galsim.ImageD(xnoise_large.bounds, scale=0.18) + rng2 = ud.duplicate() outimage.addNoise(cn) + + # Can also use applyTo syntax. + outimage2 = galsim.ImageD(xnoise_large.bounds, scale=0.18) + cn.rng.reset(rng2) + cn.applyTo(outimage2) + np.testing.assert_equal(outimage2.array, outimage.array) + + assert_raises(TypeError, cn.applyTo, outimage2.array) + assert_raises(galsim.GalSimUndefinedBoundsError, cn.applyTo, galsim.Image()) + # Summed (average) CorrelatedNoises should be approximately equal to the input, so average # multiple CFs cn_2ndlevel = galsim.CorrelatedNoise(outimage, ud, correct_periodicity=False) @@ -525,6 +544,15 @@ def test_output_generation_basic(): testim.array, refim.array, decimal=2, err_msg="Generated noise field (basic) does not match input correlation properties.") + assert_raises(TypeError, galsim.CorrelatedNoise) + assert_raises(TypeError, galsim.CorrelatedNoise, outimage.array) + assert_raises(TypeError, galsim.CorrelatedNoise, outimage, scale=1, wcs=galsim.PixelScale(3)) + assert_raises(TypeError, galsim.CorrelatedNoise, outimage, wcs=1) + assert_raises(ValueError, galsim.CorrelatedNoise, outimage, + wcs=galsim.FitsWCS('fits_files/tpv.fits')) + assert_raises(TypeError, galsim.CorrelatedNoise, outimage, rng=10) + assert_raises(ValueError, galsim.CorrelatedNoise, outimage, x_interpolant='invalid') + @timer def test_output_generation_rotated(): @@ -697,7 +725,7 @@ def test_cosmos_and_whitening(): outimage = galsim.ImageD(3 * largeim_size + 11, 3 * largeim_size, scale=cosmos_scale) outimage.addNoise(ccn) # Add the COSMOS noise # Then estimate correlation function from generated noise - cntest_correlated = galsim.CorrelatedNoise(outimage, ccn.rng) + cntest_correlated = galsim.CorrelatedNoise(outimage, ccn.rng, scale=cosmos_scale) # Check basic correlation function values of the 3x3 pixel region around (0,0) pos = galsim.PositionD(0., 0.) cf00 = ccn._profile.xValue(pos) @@ -766,11 +794,12 @@ def test_cosmos_and_whitening(): ccn_convolved = ccn_transformed.convolvedWith(galsim.Convolve([psf_ground, pix_ground])) # Reset the outimage, and set its pixel scale to now be the ground-based resolution # Also, check both odd-size and non-square here. Both should be ok. - outimage = galsim.ImageD(3 * largeim_size + 1, 3 * largeim_size + 43, scale=scale) + outimage = galsim.ImageD(3 * largeim_size + 1, 3 * largeim_size + 43) # Add correlated noise outimage.addNoise(ccn_convolved) # Then whiten - #wht_variance = ccn_convolved.whitenImage(outimage) + # Note: Use alternate syntax here. Equivalent to + # wht_variance = ccn_convolved.whitenImage(outimage) wht_variance = outimage.whitenNoise(ccn_convolved) # Then test cntest_whitened = galsim.CorrelatedNoise(outimage, ccn.rng) # Get the correlation function @@ -790,6 +819,9 @@ def test_cosmos_and_whitening(): err_msg="Noise field generated by whitening rotated, sheared, magnified, convolved "+ "COSMOS CorrelatedNoise does not have approximately zero interpixel covariances") + assert_raises(TypeError, ccn.whitenImage, outimage.array) + assert_raises(galsim.GalSimUndefinedBoundsError, ccn.whitenImage, galsim.Image()) + @timer def test_symmetrizing(): @@ -904,6 +936,12 @@ def test_symmetrizing(): err_msg="Noise field generated by symmetrizing rotated, sheared, magnified, convolved "+ "COSMOS CorrelatedNoise does not have approximate 4-fold symmetry") + assert_raises(TypeError, ccn.symmetrizeImage) + assert_raises(TypeError, ccn.symmetrizeImage, outimage.array) + assert_raises(ValueError, ccn.symmetrizeImage, galsim.Image(24,20)) + assert_raises(ValueError, ccn.symmetrizeImage, outimage, order=2) + assert_raises(ValueError, ccn.symmetrizeImage, outimage, order=5) + assert_raises(galsim.GalSimUndefinedBoundsError, ccn.symmetrizeImage, galsim.Image()) @timer def test_convolve_cosmos(): @@ -1115,6 +1153,14 @@ def test_variance_changes(): np.testing.assert_equal(cn.getVariance(), new_var, err_msg='Failure to reset and then get variance for CorrelatedNoise') + # Also check some errors here + assert_raises(ValueError, cn.withVariance, -1.0) + assert_raises(OSError, galsim.getCOSMOSNoise, file_name='not_a_file') + assert_raises(OSError, galsim.getCOSMOSNoise, file_name='config_input/catalog.fits') + assert_raises(TypeError, galsim.getCOSMOSNoise, rng='invalid') + assert_raises(ValueError, galsim.getCOSMOSNoise, variance = -1.0) + assert_raises(ValueError, galsim.getCOSMOSNoise, x_interpolant='invalid') + @timer def test_cosmos_wcs(): @@ -1171,7 +1217,7 @@ def test_cosmos_wcs(): test_im.setZero() test_im.view(wcs=cn_orig.wcs).addNoise(cn_orig) cn_test = galsim.CorrelatedNoise(test_im) - cn_raw = galsim.CorrelatedNoise(test_im.view(scale=cosmos_scale)) + cn_raw = galsim.CorrelatedNoise(test_im, wcs=galsim.PixelScale(cosmos_scale)) # This time it is the raw cf values that should match. for xpos, ypos in zip((0., cosmos_scale, 0., cosmos_scale, cosmos_scale), From ed71522eff775dfd313a87ed12d11510530ceaad Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 2 May 2018 07:59:14 -0400 Subject: [PATCH 61/96] Add GalSimNotImplementedError for things that aren't invalid, just not yet implemented (#755) --- galsim/__init__.py | 2 +- galsim/airy.py | 12 +++++---- galsim/chromatic.py | 4 +-- galsim/errors.py | 55 ++++++++++++++++++++++++++++------------- galsim/fitswcs.py | 14 +++++------ galsim/gsobject.py | 9 ++++--- galsim/lensing_ps.py | 5 ++-- galsim/scene.py | 5 ++-- galsim/shapelet.py | 7 +++--- galsim/wcs.py | 9 ++++--- tests/.coveragerc | 5 ---- tests/test_airy.py | 7 ++++++ tests/test_chromatic.py | 3 +++ tests/test_utilities.py | 8 ++++++ tests/test_wcs.py | 3 ++- 15 files changed, 95 insertions(+), 53 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index dab899767e0..4852c171112 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -103,7 +103,7 @@ # Exception and Warning classes from .errors import GalSimError, GalSimRangeError, GalSimValueError -from .errors import GalSimKeyError, GalSimIndexError +from .errors import GalSimKeyError, GalSimIndexError, GalSimNotImplementedError from .errors import GalSimBoundsError, GalSimUndefinedBoundsError, GalSimImmutableError from .errors import GalSimIncompatibleValuesError, GalSimSEDError, GalSimHSMError from .errors import GalSimConfigError, GalSimConfigValueError diff --git a/galsim/airy.py b/galsim/airy.py index 3abc2996792..45785720c29 100644 --- a/galsim/airy.py +++ b/galsim/airy.py @@ -24,7 +24,7 @@ from .gsparams import GSParams from .utilities import lazy_property, doc_inherit from .position import PositionD -from .errors import GalSimIncompatibleValuesError, convert_cpp_errors +from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError, convert_cpp_errors class Airy(GSObject): @@ -164,8 +164,9 @@ def half_light_radius(self): else: # In principle can find the half light radius as a function of lam_over_diam and # obscuration too, but it will be much more involved...! - raise NotImplementedError("Half light radius calculation not implemented for Airy " - "objects with non-zero obscuration.") + raise GalSimNotImplementedError( + "Half light radius calculation not implemented for Airy " + "objects with non-zero obscuration.") @property def fwhm(self): @@ -177,8 +178,9 @@ def fwhm(self): else: # In principle can find the FWHM as a function of lam_over_diam and obscuration too, # but it will be much more involved...! - raise NotImplementedError("FWHM calculation not implemented for Airy " - "objects with non-zero obscuration.") + raise GalSimNotImplementedError( + "FWHM calculation not implemented for Airy " + "objects with non-zero obscuration.") def __eq__(self, other): return (isinstance(other, Airy) and diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 849dfc504cc..928e572426f 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -37,7 +37,7 @@ from . import utilities from . import integ from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimValueError -from .errors import GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError, GalSimWarning class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -1900,7 +1900,7 @@ def __init__(self, *args, **kwargs): # real space convolution is not implemented for chromatic objects. real_space = kwargs.pop("real_space", None) if real_space: - raise NotImplementedError( + raise GalSimNotImplementedError( "Real space convolution of chromatic objects not implemented.") self.gsparams = kwargs.pop("gsparams", None) diff --git a/galsim/errors.py b/galsim/errors.py index eda1b98f5ed..d332e8475a2 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -38,30 +38,26 @@ # much difference in reality, and in Python 3, they made everything OSError. # We should just use OSError for all such kinds of errors. # -# KeyError: Use this for the equivalent of accessing a dict-like object with an invalid -# key. E.g. FitsHeader and Catalog raise this for accessing invalid columns. -# -# IndexError: Use this for the equivalent of accessing a list-like object with an invalid -# index. E.g. RealGalaxyCatalog and Catalog raise this for accessing invalid -# rows. -# -# NotImplementedError: Use this for features that we have not implemented. Even if there is -# no future intent to do so. E.g. GSObject defines uses this for a number -# of methods that are invalid for non-x-analytic profiles where the -# functionality is not implemented (and never will be) in some of the -# derived classes. -# Also, use it for calls that are invalid in a base class perhaps, but are -# valid for derived classes. E.g. GSObject and Position use this for their -# __init__ implementations. +# NotImplementedError: Use this for code that is not implemented by design and which will never +# be implemented. E.g. GSObject and Position use this for their __init__ +# implementations, since it is invalid to instantiate the base class. +# Use GalSimNotImplementedError for features which might someday be +# implemented. # # AttributeError: Use this only for an attempt to access an attribute that an object does not -# have. We don't currently raise this anywhere in GalSim. +# have. Like TypeError, this should be reserved for things which a more +# strongly typed language would catch at compile time. We don't currently +# raise this anywhere in GalSim. # # RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. # # ValueError: Don't use this. Use one of the below exceptions that derive from # ValueError. # +# KeyError: Don't use this. Use GalSimKeyError instead +# +# IndexError: Don't use this. Use GalSimIndexError instead. +# # std::runtime_error: Use this for errors in the C++ layer, and use the catch_cpp_errors() # context to convert these errors into GalSimErrors. E.g. # GSFitsWCS._invert_pv uses this for non-convergence, which is converted @@ -85,6 +81,13 @@ # Note: it has an optional argument to give a list of allowed values when # that is appropriate. # +# GalSimKeyError Use this for accessing a dict-like object with an invalid key. E.g. +# FitsHeader and Catalog raise this for accessing invalid columns. +# +# GalSimIndexError Use this for the equivalent of accessing a list-like object with an +# invalid index. E.g. RealGalaxyCatalog and Catalog raise this for accessing +# invalid rows. +# # GalSimRangeError: Use this when a a user provides an value outside of some allowed range. # You should also give the min/max values of the allowed range. The max # is optional, because it's not uncommon for their to be no upper limit. @@ -111,9 +114,13 @@ # # GalSimConfigError: Use this for errors processing a config dict. # -# GalSimConfigValueERror: Use this when a config dict has a value that is invalid. Basically, +# GalSimConfigValueError: Use this when a config dict has a value that is invalid. Basically, # whenever you would normally use GalSimValueError when processing # a config dict, you should use this instead. +# +# GalSimNotImplementedError Use this for features that we have not yet implemented, but which may +# be implemented someday. So it's not a necessarily invalid usage, just +# something that doesn't work currently. class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. @@ -313,6 +320,20 @@ def __reduce__(self): return GalSimConfigValueError, (self.message, self.value, self.allowed_values) +class GalSimNotImplementedError(GalSimError, NotImplementedError): + """A GalSim-specific exception class indicating that the feature being attempted is not + currently implemented. + + If this is a feature you feel you need, please open an issue about it at + + https://github.com/GalSim-developers/GalSim/issues + + Even better, feel free to offer to contribute code to implement the feature. + """ + def __repr__(self): + return 'galsim.GalSimNotImplementedError(%r)'%(str(self)) + + class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 54feb594394..d9131efd5af 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -30,7 +30,7 @@ from . import _galsim from . import fits from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimWarning, convert_cpp_errors +from .errors import GalSimNotImplementedError, GalSimWarning, convert_cpp_errors ######################################################################################### # @@ -1108,10 +1108,10 @@ def _read_tpv(self, header): if ( 'PV1_3' in header and header['PV1_3'] != 0.0 or 'PV1_11' in header and header['PV1_11'] != 0.0 or 'PV2_3' in header and header['PV1_3'] != 0.0 or - 'PV2_11' in header and header['PV1_11'] != 0.0 ): - raise NotImplementedError("TPV not implemented for odd powers of r") - if 'PV1_12' in header: - raise NotImplementedError("TPV not implemented past 3rd order terms") + 'PV2_11' in header and header['PV1_11'] != 0.0 ): # pragma: no cover + raise GalSimNotImplementedError("TPV not implemented for odd powers of r") + if 'PV1_12' in header: # pragma: no cover + raise GalSimNotImplementedError("TPV not implemented past 3rd order terms") # Another strange thing is that the two matrices are defined in the opposite order # with respect to their element ordering. And remember that we skipped k=3 in the @@ -1229,9 +1229,9 @@ def _parse_tnx_data(self, data): yorder = int(data[2].strip('.')) cross = int(data[3].strip('.')) if cross != 2: # pragma: no cover - raise NotImplementedError("TNX only implemented for half-cross option.") + raise GalSimNotImplementedError("TNX only implemented for half-cross option.") if xorder != 4 or yorder != 4: # pragma: no cover - raise NotImplementedError("TNX only implemented for order = 4") + raise GalSimNotImplementedError("TNX only implemented for order = 4") # Note: order = 4 really means cubic. order is how large the pv matrix is, i.e. 4x4. xmin = float(data[4]) diff --git a/galsim/gsobject.py b/galsim/gsobject.py index e8139be9c7c..3beca7ee358 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -59,7 +59,7 @@ from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimWarning, convert_cpp_errors +from .errors import GalSimNotImplementedError, GalSimWarning, convert_cpp_errors class GSObject(object): @@ -2122,9 +2122,10 @@ def drawPhot(self, image, gain=1., add_to_image=False, try: photons = self.shoot(thisN, ud) except (GalSimError, NotImplementedError) as e: - raise GalSimError("Unable to draw this GSObject with photon shooting. Perhaps it " - "is a Deconvolve or is a compound including one or more " - "Deconvolve objects.\nOriginal error: %r"%(e)) + raise GalSimNotImplementedError( + "Unable to draw this GSObject with photon shooting. Perhaps it " + "is a Deconvolve or is a compound including one or more " + "Deconvolve objects.\nOriginal error: %r"%(e)) if g != 1. or thisN != Ntot: photons.scaleFlux(g * thisN / Ntot) diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index f89db7fd1b4..ac52855504e 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -31,7 +31,8 @@ from .table import LookupTable from . import utilities from . import integ -from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimNotImplementedError, GalSimWarning def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -1534,7 +1535,7 @@ def kappaKaiserSquires(g1, g2): if g1.shape != g2.shape: raise GalSimIncompatibleValuesError("Input g1 and g2 must be the same shape.", g1=g1, g2=g2) if g1.shape[0] != g1.shape[1]: - raise NotImplementedError("Non-square input shear grids not supported.") + raise GalSimNotImplementedError("Non-square input shear grids not supported.") # Then setup the kx, ky grids kx, ky = utilities.kxky(g1.shape) diff --git a/galsim/scene.py b/galsim/scene.py index 73f6121c661..65de2ea1ad5 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -26,7 +26,8 @@ import os from .real import RealGalaxy, RealGalaxyCatalog -from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError +from .errors import GalSimNotImplementedError, GalSimWarning # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -475,7 +476,7 @@ def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size # call the appropriate helper routine for that case. if gal_type == 'real': if chromatic: - raise NotImplementedError("Cannot yet make real chromatic galaxies!") + raise GalSimNotImplementedError("Cannot yet make real chromatic galaxies!") gal_list = [] for idx in indices: real_params = self.getRealParams(idx) diff --git a/galsim/shapelet.py b/galsim/shapelet.py index 692ecae2b20..7486265b671 100644 --- a/galsim/shapelet.py +++ b/galsim/shapelet.py @@ -28,7 +28,8 @@ from .image import Image from .utilities import doc_inherit from . import _galsim -from .errors import GalSimValueError, GalSimIncompatibleValuesError, convert_cpp_errors +from .errors import GalSimValueError, GalSimIncompatibleValuesError, GalSimNotImplementedError +from .errors import convert_cpp_errors class Shapelet(GSObject): @@ -284,8 +285,8 @@ def fit(cls, sigma, order, image, center=None, normalization='flux', gsparams=No if image.wcs is not None and not image.wcs.isPixelScale(): # TODO: Add ability for ShapeletFitImage to take jacobian matrix. - raise NotImplementedError("Sorry, cannot (yet) fit a shapelet model to an image " - "with a non-trivial WCS.") + raise GalSimNotImplementedError("Sorry, cannot (yet) fit a shapelet model to an image " + "with a non-trivial WCS.") # Make it double precision if it is not. image = Image(image, dtype=np.float64, copy=False) diff --git a/galsim/wcs.py b/galsim/wcs.py index f232d0251af..6fbad16c726 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -53,7 +53,7 @@ from .position import Position, PositionI, PositionD from .celestial import CelestialCoord from .shear import Shear -from .errors import GalSimError, GalSimIncompatibleValuesError +from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimNotImplementedError class BaseWCS(object): """The base class for all other kinds of WCS transformations. @@ -2170,7 +2170,7 @@ def _v(self, x, y, color=None): def _x(self, u, v, color=None): if self._xfunc is None: - raise NotImplementedError( + raise GalSimNotImplementedError( "World -> Image direction not implemented for this UVFunction") else: if self._uses_color: @@ -2180,7 +2180,7 @@ def _x(self, u, v, color=None): def _y(self, u, v, color=None): if self._yfunc is None: - raise NotImplementedError( + raise GalSimNotImplementedError( "World -> Image direction not implemented for this UVFunction") else: if self._uses_color: @@ -2343,7 +2343,8 @@ def _radec(self, x, y, color=None): return self._radec_func(x,y) def _xy(self, ra, dec, color=None): - raise NotImplementedError("World -> Image direction not implemented for RaDecFunction") + raise GalSimNotImplementedError( + "World -> Image direction not implemented for RaDecFunction") def _newOrigin(self, origin): return RaDecFunction(self._orig_ra_func, self._orig_dec_func, origin) diff --git a/tests/.coveragerc b/tests/.coveragerc index af30d247ca5..5419b6c58c7 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -31,11 +31,6 @@ exclude_lines = # Don't complain about missing debug-only code: logger.debug - # Don't complain if tests don't hit defensive checks of user input - raise NotImplementedError - raise TypeError - raise OSError - # Don't complain about not hitting warning code if suppress_warnings is False: import warnings diff --git a/tests/test_airy.py b/tests/test_airy.py index 6d60894fac2..d4edb9d26ff 100644 --- a/tests/test_airy.py +++ b/tests/test_airy.py @@ -158,6 +158,13 @@ def test_airy_radii(): assert_raises(AttributeError, getattr, test_gal_shear, "half_light_radius") assert_raises(AttributeError, getattr, test_gal_shear, "lam_over_diam") + # hlr and fwhm not implemented for obscuration != 0 + airy2 = galsim.Airy(lam_over_diam= 1./0.8, flux=1., obscuration=0.2) + with assert_raises(galsim.GalSimNotImplementedError): + airy2.half_light_radius + with assert_raises(galsim.GalSimNotImplementedError): + airy2.fwhm + @timer def test_airy_flux_scaling(): diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 28cce845acd..e7de53f7e18 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -215,6 +215,9 @@ def test_draw_add_commutativity(): assert_raises(TypeError, chromatic_final.drawKImage, bandpass, integrator=galsim.integ.midpt) + # Can't use base class directly. + assert_raises(NotImplementedError, galsim.integ.ImageIntegrator) + @timer def test_ChromaticConvolution_InterpolatedImage(): diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 56648bb21e3..6a84dbb9619 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -83,6 +83,10 @@ def test_pos(): assert_raises(TypeError, galsim.PositionD, 11) assert_raises(ValueError, galsim.PositionD, 11, "blue") + # Can't use base class directly. + assert_raises(TypeError, galsim.Position, 11, 23) + assert_raises(NotImplementedError, galsim.Position) + # Check arithmetic for p1 in [pi1, pd1]: @@ -219,6 +223,10 @@ def test_bounds(): assert_raises(TypeError, galsim.BoundsD, 11) assert_raises(ValueError, galsim.BoundsD, 11, 23, 17, "blue") + # Can't use base class directly. + assert_raises(TypeError, galsim.Bounds, 11, 23, 9, 12) + assert_raises(NotImplementedError, galsim.Bounds) + # Check intersection assert bi1 == galsim.BoundsI(0,100,0,100) & bi1 assert bi1 == bi1 & galsim.BoundsI(0,100,0,100) diff --git a/tests/test_wcs.py b/tests/test_wcs.py index a0b3053a07c..62a9bc77830 100644 --- a/tests/test_wcs.py +++ b/tests/test_wcs.py @@ -210,7 +210,8 @@ def do_wcs_pos(wcs, ufunc, vfunc, name, x0=0, y0=0, color=None): image_pos.y*scale, image_pos3.y*scale, digits2, 'wcs.posToImage returned wrong image position for '+name) except NotImplementedError: - pass + assert_raises(NotImplementedError, wcs._x, world_pos.x, world_pos.y, color=color) + assert_raises(NotImplementedError, wcs._y, world_pos.x, world_pos.y, color=color) if x0 == 0 and y0 == 0: # The last item in list should also work as a PositionI From 0f3f7529d05226b7acb7242db7e87b18637f0e0b Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 2 May 2018 21:13:37 -0400 Subject: [PATCH 62/96] Some fixes for Python 3 (#755) --- galsim/catalog.py | 27 +++++++++++++++--------- galsim/des/des_psfex.py | 5 ++--- galsim/errors.py | 20 ++++++++++++++++-- galsim/real.py | 12 +++-------- galsim/wcs.py | 2 +- tests/test_catalog.py | 8 +++++-- tests/test_des.py | 2 +- tests/test_errors.py | 46 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 94 insertions(+), 28 deletions(-) diff --git a/galsim/catalog.py b/galsim/catalog.py index 8843c08ed2f..13d2e4f50e0 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -84,6 +84,7 @@ def __init__(self, file_name, dir=None, file_type=None, comments='#', hdu=1, raise GalSimValueError("file_type must be either FITS or ASCII if specified.", file_type, ('FITS', 'ASCII')) self.file_type = file_type + if comments == '': comments = None # loadtxt actually wants None, not '' self.comments = comments self.hdu = hdu @@ -101,7 +102,7 @@ def __len__(self) : return self.nobjects def readAscii(self, comments, _nobjects_only=False): """Read in an input catalog from an ASCII file. """ - if len(comments) > 1: + if comments is not None and len(comments) > 1: raise GalSimValueError('Invalid comments character', comments) # If all we care about is nobjects, this is quicker: @@ -110,10 +111,10 @@ def readAscii(self, comments, _nobjects_only=False): # An even faster version using buffering is possible although it requires some care # around edge cases, so we use this one instead, which is "correct by inspection". with open(self.file_name) as f: - if (len(comments) == 1): + if comments is not None: c = comments[0] self.nobjects = sum(1 for line in f if line[0] != c) - else: # len(comments) == 0. No comments. + else: # comments == None. No comments. self.nobjects = sum(1 for line in f) return @@ -158,17 +159,23 @@ def get(self, index, col): """ if self.isfits: if col not in self.names: - raise GalSimKeyError("Column %s is invalid for catalog %s"%(col,self.file_name)) + raise GalSimKeyError("Column is invalid for catalog %s"%self.file_name, col) + if not isinstance(index, int): + raise GalSimIndexError("Index must be an int for catalog %s"%self.file_name, index) if index < 0 or index >= self.nobjects: - raise GalSimIndexError("Object %d is invalid for catalog %s"%(index,self.file_name)) + raise GalSimIndexError("Index is invalid for catalog %s"%self.file_name, index) return self.data[col][index] else: - icol = int(col) - if icol < 0 or icol >= self.ncols: - raise GalSimIndexError("Column %s is invalid for catalog %s"%(icol,self.file_name)) + if not isinstance(col, int): + raise GalSimIndexError("Column must an int for ASCII catalog %s"%self.file_name, + col) + if col < 0 or col >= self.ncols: + raise GalSimIndexError("Column is invalid for catalog %s"%self.file_name, col) + if not isinstance(index, int): + raise GalSimIndexError("Index must be an int for catalog %s"%self.file_name, index) if index < 0 or index >= self.nobjects: - raise GalSimIndexError("Object %s is invalid for catalog %s"%(index,self.file_name)) - return self.data[index, icol] + raise GalSimIndexError("Index is invalid for catalog %s"%self.file_name, col) + return self.data[index, col] def getFloat(self, index, col): """Return the data for the given `index` and `col` as a float if possible diff --git a/galsim/des/des_psfex.py b/galsim/des/des_psfex.py index eb800ce3373..414b59ce9ac 100644 --- a/galsim/des/des_psfex.py +++ b/galsim/des/des_psfex.py @@ -27,7 +27,6 @@ https://www.astromatic.net/pubsvn/software/psfex/trunk/doc/psfex.pdf """ -from past.builtins import basestring import os import numpy as np @@ -107,7 +106,7 @@ class DES_PSFEx(object): def __init__(self, file_name, image_file_name=None, wcs=None, dir=None): if dir: - if not isinstance(file_name, basestring): + if not isinstance(file_name, str): raise TypeError("file_name must be a string") file_name = os.path.join(dir,file_name) if image_file_name is not None: @@ -129,7 +128,7 @@ def __init__(self, file_name, image_file_name=None, wcs=None, dir=None): def read(self): from galsim._pyfits import pyfits - if isinstance(self.file_name, basestring): + if isinstance(self.file_name, str): hdu_list = pyfits.open(self.file_name) hdu = hdu_list[1] else: diff --git a/galsim/errors.py b/galsim/errors.py index d332e8475a2..2f13b3e974d 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -159,16 +159,32 @@ class GalSimKeyError(GalSimError, KeyError): """A GalSim-specific exception class indicating an attempt to access a dict-like object with an invalid key. """ + def __init__(self, message, key): + self.message = message + self.key = key + super().__init__(message, key) # Need to pass key or pickle fails. + + def __str__(self): + return self.message + " Key {0!s}".format(self.key) + def __repr__(self): - return 'galsim.GalSimKeyError(%r)'%(str(self)) + return 'galsim.GalSimKeyError(%r,%r)'%(self.message, self.key) class GalSimIndexError(GalSimError, IndexError): """A GalSim-specific execption class indicating an attempt to access a list-like object with an invalid index. """ + def __init__(self, message, index): + self.message = message + self.index = index + super().__init__(message, index) + + def __str__(self): + return self.message + " Index {0!s}".format(self.index) + def __repr__(self): - return 'galsim.GalSimIndexError(%r)'%(str(self)) + return 'galsim.GalSimIndexError(%r,%r)'%(self.message, self.index) class GalSimRangeError(GalSimError, ValueError): diff --git a/galsim/real.py b/galsim/real.py index 654e054e165..af48c543efc 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -716,9 +716,7 @@ def getGalImage(self, i): if self.logger: self.logger.debug('RealGalaxyCatalog %d: Start getGalImage',i) if i >= len(self.gal_file_name): - raise GalSimIndexError( - 'index %d given to getGalImage is out of range (0..%d)' - % (i,len(self.gal_file_name)-1)) + raise GalSimIndexError('index out of range (0..%d)'%(len(self.gal_file_name)-1),i) f = self._getFile(self.gal_file_name[i]) # For some reason the more elegant `with gal_lock:` syntax isn't working for me. # It gives an EOFError. But doing an explicit acquire and release seems to work fine. @@ -735,9 +733,7 @@ def getPSFImage(self, i): if self.logger: self.logger.debug('RealGalaxyCatalog %d: Start getPSFImage',i) if i >= len(self.psf_file_name): - raise GalSimIndexError( - 'index %d given to getPSFImage is out of range (0..%d)' - % (i,len(self.psf_file_name)-1)) + raise GalSimIndexError('index out of range (0..%d)'%(len(self.psf_file_name)-1),i) f = self._getFile(self.psf_file_name[i]) self.psf_lock.acquire() array = f[self.psf_hdu[i]].data @@ -765,9 +761,7 @@ def getNoiseProperties(self, i): im = None else: if i >= len(self.noise_file_name): - raise GalSimIndexError( - 'index %d given to getNoise is out of range (0..%d)'%( - i,len(self.noise_file_name)-1)) + raise GalSimIndexError('index out of range (0..%d)'%(len(self.noise_file_name)-1),i) if self.noise_file_name[i] in self.saved_noise_im: im = self.saved_noise_im[self.noise_file_name[i]] if self.logger: diff --git a/galsim/wcs.py b/galsim/wcs.py index 6fbad16c726..c9a876d76b3 100644 --- a/galsim/wcs.py +++ b/galsim/wcs.py @@ -1971,7 +1971,7 @@ def _writeFuncToHeader(func, letter, header): # Fits can't handle arbitrary strings. Shrink to a base-64 alphabet that is printable. # (This is like UUencoding for those of you who remember that...) - s = base64.b64encode(s) + s = base64.b64encode(s).decode() first_key = 'GS_'+letter+'_FN' else: # Nothing to write. diff --git a/tests/test_catalog.py b/tests/test_catalog.py index bbd8de917fa..575228d590c 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -63,6 +63,8 @@ def test_ascii_catalog(): cat.get(1, 50) with assert_raises(IndexError): cat.get('val', 11) + with assert_raises(IndexError): + cat.get(3, 'val') cat2 = galsim.Catalog('catalog2.txt', 'config_input', comments='%') assert len(cat2) == cat2.nobjects == cat.nobjects @@ -76,7 +78,7 @@ def test_ascii_catalog(): assert cat3 != cat do_pickle(cat3) - cat3n = galsim.Catalog('catalog3.txt', 'config_input', comments='', _nobjects_only=True) + cat3n = galsim.Catalog('catalog3.txt', 'config_input', comments=None, _nobjects_only=True) assert cat3n.nobjects == 3 with assert_raises((IOError, OSError)): @@ -117,7 +119,9 @@ def test_fits_catalog(): cat.get(3, 'angle2') with assert_raises(KeyError): cat.get(1, 'invalid') - with assert_raises(TypeError): + with assert_raises(KeyError): + cat.get(1, 3) + with assert_raises(IndexError): cat.get('val', 'angle2') cat2 = galsim.Catalog('catalog2.fits', 'config_input', hdu=2) diff --git a/tests/test_des.py b/tests/test_des.py index d6cbe88e212..ba0517226be 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -635,7 +635,7 @@ def test_psf(): err_msg="no-wcs PSFEx shape.g2 doesn't match") with assert_raises(TypeError): - galsim.des.DES_PSFEx(file_name=file, wcs=wcs_file, dir=data_dir) + galsim.des.DES_PSFEx(psf, wcs=wcs_file, dir=data_dir) with assert_raises(galsim.GalSimError): galsim.des.DES_PSFEx(psfex_file, image_file_name=wcs_file, wcs=wcs_file, dir=data_dir) diff --git a/tests/test_errors.py b/tests/test_errors.py index 0a00fab727d..f06925faf08 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -59,6 +59,36 @@ def test_galsim_value_error(): do_pickle(err) +@timer +def test_galsim_key_error(): + """Test basic usage of GalSimKeyError + """ + key = 'foo' + err = galsim.GalSimKeyError("Test", key) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Key foo" + assert err.key == key + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, KeyError) + do_pickle(err) + + +@timer +def test_galsim_index_error(): + """Test basic usage of GalSimIndexError + """ + index = 3 + err = galsim.GalSimIndexError("Test", index) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test Index 3" + assert err.index == index + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, IndexError) + do_pickle(err) + + @timer def test_galsim_range_error(): """Test basic usage of GalSimRangeError @@ -216,6 +246,19 @@ def test_galsim_config_value_error(): do_pickle(err) +@timer +def test_galsim_notimplemented_error(): + """Test basic usage of GalSimNotImplementedError + """ + err = galsim.GalSimNotImplementedError("Test") + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == "Test" + assert isinstance(err, galsim.GalSimError) + assert isinstance(err, NotImplementedError) + do_pickle(err) + + @timer def test_galsim_warning(): """Test basic usage of GalSimWarning @@ -243,6 +286,8 @@ def test_galsim_deprecation_warning(): if __name__ == "__main__": test_galsim_error() test_galsim_value_error() + test_galsim_key_error() + test_galsim_index_error() test_galsim_range_error() test_galsim_bounds_error() test_galsim_undefined_bounds_error() @@ -252,5 +297,6 @@ def test_galsim_deprecation_warning(): test_galsim_hsm_error() test_galsim_config_error() test_galsim_config_value_error() + test_galsim_notimplemented_error() test_galsim_warning() test_galsim_deprecation_warning() From ad68e14c91928e702afdc3e09a84e2096ca0515b Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 20:02:55 -0400 Subject: [PATCH 63/96] Clean up gzip and bzip2 file handling. (#755) --- galsim/fits.py | 283 ++++++++++++++++--------------------------------- 1 file changed, 93 insertions(+), 190 deletions(-) diff --git a/galsim/fits.py b/galsim/fits.py index 386aeeca570..4855b58e018 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -75,12 +75,25 @@ def gunzip_call(self, file): # (with zcat being a symlink to uncompress instead). # Also, I'd rather all these use `with subprocess.Popen(...) as p:`, but that's not # supported in 2.7. So need to keep things this way for now. - p = subprocess.Popen(["gunzip", "-c", file], stdout=subprocess.PIPE, close_fds=True) - fin = BytesIO(p.communicate()[0]) - if p.returncode != 0: + try: + p = subprocess.Popen(["gunzip", "-c", file], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + except OSError: + # This OSError should mean that the gunzip call itself was invalid on this system. + # Convert to a NotImplementedError, so we can try a different method. + raise NotImplementedError() + ret = p.communicate() + if ret == '': + raise OSError("Error running gunzip. stderr output = %s"%ret[1]) + if p.returncode != 0: # pragma: no cover raise OSError("Error running gunzip. Return code = %s"%p.returncode) + fin = BytesIO(ret[0]) p.wait() - hdu_list = pyfits.open(fin, 'readonly') + try: + hdu_list = pyfits.open(fin, 'readonly') + except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover + # In case astropy fails. + raise NotImplementedError() return hdu_list, fin # Note: the above gzip_call function succeeds on travis, so the rest don't get run. @@ -90,100 +103,63 @@ def gzip_in_mem(self, file): # pragma: no cover from ._pyfits import pyfits fin = gzip.open(file, 'rb') hdu_list = pyfits.open(fin, 'readonly') - # Sometimes this doesn't work. The symptoms may be that this raises an - # exception, or possibly the hdu_list comes back empty, in which case the - # next line will raise an exception. - hdu = hdu_list[0] # pyfits doesn't actually read the file yet, so we can't close fin here. # Need to pass it back to the caller and let them close it when they are # done with hdu_list. return hdu_list, fin - def pyfits_open(self, file): # pragma: no cover - from ._pyfits import pyfits - # This usually works, although pyfits internally may (depending on the version) - # use a temporary file, which is why we prefer the above in-memory code if it works. - # For some versions of pyfits, this is actually the same as the in_mem version. - hdu_list = pyfits.open(file, 'readonly') - return hdu_list, None - - def gzip_tmp(self, file): # pragma: no cover - import gzip - from ._pyfits import pyfits - # Finally, just in case, if everything else failed, here is an implementation that - # should always work. - fin = gzip.open(file, 'rb') - data = fin.read() - tmp = file + '.tmp' - # It would be pretty odd for this filename to already exist, but just in case... - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - with open(tmp,"w") as tmpout: - tmpout.write(data) - hdu_list = pyfits.open(tmp) - return hdu_list, tmp - def bunzip2_call(self, file): import subprocess from io import BytesIO from ._pyfits import pyfits - p = subprocess.Popen(["bunzip2", "-c", file], stdout=subprocess.PIPE, close_fds=True) - fin = BytesIO(p.communicate()[0]) - if p.returncode != 0: + try: + p = subprocess.Popen(["bunzip2", "-c", file], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + except OSError: + # This OSError should mean that the gunzip call itself was invalid on this system. + # Convert to a NotImplementedError, so we can try a different method. + raise NotImplementedError() + ret = p.communicate() + if ret == '': + raise OSError("Error running bunzip2. stderr output = %s"%ret[1]) + if p.returncode != 0: # pragma: no cover raise OSError("Error running bunzip2. Return code = %s"%p.returncode) + fin = BytesIO(ret[0]) p.wait() - hdu_list = pyfits.open(fin, 'readonly') + try: + hdu_list = pyfits.open(fin, 'readonly') + except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover + # In case astropy fails. + raise NotImplementedError() return hdu_list, fin def bz2_in_mem(self, file): # pragma: no cover import bz2 from ._pyfits import pyfits - # This normally works. But it might not on old versions of pyfits. fin = bz2.BZ2File(file, 'rb') hdu_list = pyfits.open(fin, 'readonly') - # Sometimes this doesn't work. The symptoms may be that this raises an - # exception, or possibly the hdu_list comes back empty, in which case the - # next line will raise an exception. - hdu = hdu_list[0] return hdu_list, fin - def bz2_tmp(self, file): # pragma: no cover - import bz2 - from ._pyfits import pyfits - fin = bz2.BZ2File(file, 'rb') - data = fin.read() - tmp = file + '.tmp' - # It would be pretty odd for this filename to already exist, but just in case... - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - with open(tmp,"w") as tmpout: - tmpout.write(data) - hdu_list = pyfits.open(tmp) - return hdu_list, tmp - def __init__(self): - # For each compression type, we try them in rough order of efficiency and keep track of - # which method worked for next time. Whenever one doesn't work, we increment the - # method number and try the next one. The *_call methods are usually the fastest, - # sometimes much, much faster than the *_in_mem version. At least for largish files, - # which are precisely the ones that people would most likely want to compress. - # However, we can't require the user to have the system executables installed. So if - # that fails, we move on to the other options. It varies which of the other options - # is fastest, but they all usually succeed, which is the most important thing for a - # backup method, so it probably doesn't matter much what order we do the rest. + # We used to have multiple options for gzip and bzip2. However, with recent versions of + # astropy for the fits I/O, the in memory version should always work. So we first + # try the command line method, which is usually faster. Then if that fails, we let + # astropy do the compression. self.gz_index = 0 self.bz2_index = 0 - self.gz_methods = [self.gunzip_call, self.gzip_in_mem, self.pyfits_open, self.gzip_tmp] - self.bz2_methods = [self.bunzip2_call, self.bz2_in_mem, self.bz2_tmp] + self.gz_methods = [self.gunzip_call, self.gzip_in_mem] + self.bz2_methods = [self.bunzip2_call, self.bz2_in_mem] self.gz = self.gz_methods[0] self.bz2 = self.bz2_methods[0] def __call__(self, file, dir, file_compress): from ._pyfits import pyfits if dir: - import os file = os.path.join(dir,file) + if not os.path.isfile(file): + raise OSError("File %s not found"%file) + if not file_compress: hdu_list = pyfits.open(file, 'readonly') return hdu_list, None @@ -195,11 +171,12 @@ def __call__(self, file, dir, file_compress): while self.gz_index < len(self.gz_methods): try: return self.gz(file) - except KeyboardInterrupt: - raise - except: # pragma: no cover - self.gz_index += 1 - self.gz = self.gz_methods[self.gz_index] + except (ImportError, NotImplementedError): # pragma: no cover + if self.gz_index == len(self.gz_methods-1): + raise + else: + self.gz_index += 1 + self.gz = self.gz_methods[self.gz_index] else: # pragma: no cover raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': @@ -207,11 +184,12 @@ def __call__(self, file, dir, file_compress): while self.bz2_index < len(self.bz2_methods): try: return self.bz2(file) - except KeyboardInterrupt: - raise - except: # pragma: no cover - self.bz2_index += 1 - self.bz2 = self.bz2_methods[self.bz2_index] + except (ImportError, NotImplementedError): # pragma: no cover + if self.bz2_index == len(self.bz2_methods-1): + raise + else: + self.bz2_index += 1 + self.bz2 = self.bz2_methods[self.bz2_index] else: # pragma: no cover raise GalSimError("None of the options for bunzipping were successful.") else: # pragma: no cover (can't get here from public API) @@ -222,36 +200,20 @@ def __call__(self, file, dir, file_compress): class _WriteFile: # There are several methods available for each of gzip and bzip2. Each is its own function. - def gzip_call2(self, hdu_list, file): # pragma: no cover - root, ext = os.path.splitext(file) - import subprocess - if os.path.isfile(root): - tmp = root + '.tmp' - # It would be pretty odd for this filename to already exist, but just in case... - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - hdu_list.writeto(tmp) - p = subprocess.Popen(["gzip", tmp], close_fds=True) - p.communicate() - if p.returncode != 0: - raise OSError("Error running gzip. Return code = %s"%p.returncode) - p.wait() - os.rename(tmp+".gz",file) - else: - hdu_list.writeto(root) - p = subprocess.Popen(["gzip", "-S", ext, "-f", root], close_fds=True) - p.communicate() - if p.returncode != 0: - raise OSError("Error running gzip. Return code = %s"%p.returncode) - p.wait() - def gzip_call(self, hdu_list, file): import subprocess with open(file, 'wb') as fout: - p = subprocess.Popen(["gzip", "-"], stdin=subprocess.PIPE, stdout=fout, close_fds=True) - hdu_list.writeto(p.stdin) + try: + p = subprocess.Popen(["gzip", "-"], stdin=subprocess.PIPE, stdout=fout, + close_fds=True) + hdu_list.writeto(p.stdin) + except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover + # This OSError should mean that the gunzip call itself was invalid on this system. + # Convert to a NotImplementedError, so we can try a different method. + # The others are in case astropy fails. + raise NotImplementedError() p.communicate() - if p.returncode != 0: + if p.returncode != 0: # pragma: no cover raise OSError("Error running gzip. Return code = %s"%p.returncode) p.wait() @@ -268,51 +230,19 @@ def gzip_in_mem(self, hdu_list, file): # pragma: no cover with gzip.open(file, 'wb') as fout: fout.write(data) - def gzip_tmp(self, hdu_list, file): # pragma: no cover - import gzip - # Old pyfits versions did not support writing to a buffer, so the # above code will fail. - # This is probably not necessary anymore, but left here as another option in case useful. - tmp = file + '.tmp' - # It would be pretty odd for this filename to already exist, but just in case... - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - hdu_list.writeto(tmp) - with open(tmp,"r") as buf: - data = buf.read() - os.remove(tmp) - with gzip.open(file, 'wb') as fout: - fout.write(data) - - def bzip2_call2(self, hdu_list, file): # pragma: no cover - root, ext = os.path.splitext(file) - import subprocess - if os.path.isfile(root) or ext != '.bz2': - tmp = root + '.tmp' - # It would be pretty odd for this filename to already exist, but just in case... - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - hdu_list.writeto(tmp) - p = subprocess.Popen(["bzip2", tmp], close_fds=True) - p.communicate() - if p.returncode != 0: - raise OSError("Error running bzip2. Return code = %s"%p.returncode) - p.wait() - os.rename(tmp+".bz2",file) - else: - hdu_list.writeto(root) - p = subprocess.Popen(["bzip2", root], close_fds=True) - p.communicate() - if p.returncode != 0: - raise OSError("Error running bzip2. Return code = %s"%p.returncode) - p.wait() - def bzip2_call(self, hdu_list, file): import subprocess with open(file, 'wb') as fout: - p = subprocess.Popen(["bzip2"], stdin=subprocess.PIPE, stdout=fout, close_fds=True) - hdu_list.writeto(p.stdin) + try: + p = subprocess.Popen(["bzip2"], stdin=subprocess.PIPE, stdout=fout, close_fds=True) + hdu_list.writeto(p.stdin) + except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover + # This OSError should mean that the gunzip call itself was invalid on this system. + # Convert to a NotImplementedError, so we can try a different method. + # The others are in case astropy fails. + raise NotImplementedError() p.communicate() - if p.returncode != 0: + if p.returncode != 0: # pragma: no cover raise OSError("Error running bzip2. Return code = %s"%p.returncode) p.wait() @@ -325,39 +255,19 @@ def bz2_in_mem(self, hdu_list, file): # pragma: no cover with bz2.BZ2File(file, 'wb') as fout: fout.write(data) - def bz2_tmp(self, hdu_list, file): # pragma: no cover - import bz2 - tmp = file + '.tmp' - while os.path.isfile(tmp): - tmp = tmp + '.tmp' - hdu_list.writeto(tmp) - with open(tmp,"r") as buf: - data = buf.read() - os.remove(tmp) - with bz2.BZ2File(file, 'wb') as fout: - fout.write(data) - def __init__(self): - # For each compression type, we try them in rough order of efficiency and keep track of - # which method worked for next time. Whenever one doesn't work, we increment the - # method number and try the next one. The *_call methods seem to be usually the fastest, - # and we expect that they will usually work. However, we can't require the user - # to have the system executables. Also, some versions of pyfits can't handle writing - # to the stdin pipe of a subprocess. So if that fails, the next one, *_call2 is often - # fastest if the failure was due to pyfits. If the user does not have gzip or bzip2 (then - # why are they requesting this compression?), we switch to *_in_mem, which is often - # almost as good. (Sometimes it is faster than the call2 option, but when it is slower it - # can be much slower.) And finally, if this fails, which I think may happen for very old - # versions of pyfits, *_tmp is the fallback option. + # Again, we used to have a number of methods here for gzip and bzip2, but now only two. + # We first try using a command-line call to either gzip or bzip2. But if that doesn't + # work, we use either the gzip or bz2 module in memory, which is usually not quite as + # fast, but should always work. self.gz_index = 0 self.bz2_index = 0 - self.gz_methods = [self.gzip_call, self.gzip_call2, self.gzip_in_mem, self.gzip_tmp] - self.bz2_methods = [self.bzip2_call, self.bzip2_call2, self.bz2_in_mem, self.bz2_tmp] + self.gz_methods = [self.gzip_call, self.gzip_in_mem] + self.bz2_methods = [self.bzip2_call, self.bz2_in_mem] self.gz = self.gz_methods[0] self.bz2 = self.bz2_methods[0] def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress): - import os if dir: file = os.path.join(dir,file) @@ -373,22 +283,24 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) while self.gz_index < len(self.gz_methods): try: return self.gz(hdu_list, file) - except KeyboardInterrupt: - raise - except: # pragma: no cover - self.gz_index += 1 - self.gz = self.gz_methods[self.gz_index] + except (ImportError, NotImplementedError): # pragma: no cover + if self.gz_index == len(self.gz_methods)-1: + raise + else: + self.gz_index += 1 + self.gz = self.gz_methods[self.gz_index] else: # pragma: no cover raise GalSimError("None of the options for gunzipping were successful.") elif file_compress == 'bzip2': while self.bz2_index < len(self.bz2_methods): try: return self.bz2(hdu_list, file) - except KeyboardInterrupt: - raise - except: # pragma: no cover - self.bz2_index += 1 - self.bz2 = self.bz2_methods[self.bz2_index] + except (ImportError, NotImplementedError): # pragma: no cover + if self.bz2_index == len(self.bz2_methods)-1: + raise + else: + self.bz2_index += 1 + self.bz2 = self.bz2_methods[self.bz2_index] else: # pragma: no cover raise GalSimError("None of the options for bunzipping were successful.") else: # pragma: no cover (can't get here from public API) @@ -461,14 +373,7 @@ def closeHDUList(hdu_list, fin): """If necessary, close the file handle that was opened to read in the `hdu_list`""" hdu_list.close() if fin: - if isinstance(fin, basestring): # pragma: no cover - # In this case, it is a file name that we need to delete. - # Note: This is relevant for the _tmp versions that are not run on Travis, so - # don't include this bit in the coverage report. - import os - os.remove(fin) - else: - fin.close() + fin.close() ############################################################################################## # @@ -1163,7 +1068,6 @@ def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=Non if file_name is not None: if dir is not None: - import os self._tag = 'file_name='+repr(os.path.join(dir,file_name)) else: self._tag = 'file_name='+repr(file_name) @@ -1175,7 +1079,6 @@ def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=Non if text_file: self._tag += ', text_file=True' if dir is not None: - import os file_name = os.path.join(dir,file_name) with open(file_name,"r") as fin: lines = [ line.strip() for line in fin ] From 4e32c9ce19a44ee1d51eddaf44108097fdd2b224 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 20:33:43 -0400 Subject: [PATCH 64/96] Fix des_meds usage with no PSF (#755) --- galsim/des/des_meds.py | 23 +++++++++++++---------- tests/test_des.py | 5 +++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index a1f9f2e42a8..c94e92cf720 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -308,7 +308,8 @@ def WriteMEDS(obj_list, file_name, clobber=True): vec['image'] = np.concatenate(vec['image']) vec['seg'] = np.concatenate(vec['seg']) vec['weight'] = np.concatenate(vec['weight']) - vec['psf'] = np.concatenate(vec['psf']) + if obj.psf is not None: + vec['psf'] = np.concatenate(vec['psf']) # get the primary HDU primary = pyfits.PrimaryHDU() @@ -417,13 +418,11 @@ def WriteMEDS(obj_list, file_name, clobber=True): metadata.update_ext_name('metadata') # rest of HDUs are image vectors - image_cutouts = pyfits.ImageHDU( vec['image'] , name='image_cutouts' ) - weight_cutouts = pyfits.ImageHDU( vec['weight'], name='weight_cutouts' ) - seg_cutouts = pyfits.ImageHDU( vec['seg'] , name='seg_cutouts' ) - psf_cutouts = pyfits.ImageHDU( vec['psf'] , name='psf' ) + image_cutouts = pyfits.ImageHDU( vec['image'] , name='image_cutouts') + weight_cutouts = pyfits.ImageHDU( vec['weight'], name='weight_cutouts') + seg_cutouts = pyfits.ImageHDU( vec['seg'] , name='seg_cutouts') - # write all - hdu_list = pyfits.HDUList([ + hdu_list = [ primary, object_data, image_info, @@ -431,9 +430,13 @@ def WriteMEDS(obj_list, file_name, clobber=True): image_cutouts, weight_cutouts, seg_cutouts, - psf_cutouts - ]) - galsim.fits.writeFile(file_name, hdu_list) + ] + + if obj.psf is not None: + psf_cutouts = pyfits.ImageHDU( vec['psf'], name='psf') + hdu_list.append(psf_cutouts) + + galsim.fits.writeFile(file_name, pyfits.HDUList(hdu_list)) # Make the class that will diff --git a/tests/test_des.py b/tests/test_des.py index ba0517226be..6b74ea2a51f 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -148,6 +148,10 @@ def test_meds(): with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11], wcs=[celestial_wcs]) + # Check the one with no psf, weight, etc. + filename_meds2 = 'output/test_meds_image_only.fits' + galsim.des.WriteMEDS([obj3], filename_meds2, clobber=True) + # Note that while there are no tests prior to this, the above still checks for # syntax errors in the meds creation software, so it's still worth running as part @@ -165,6 +169,7 @@ def test_meds(): # Run meds module's validate function try: meds.util.validate_meds(filename_meds) + meds.util.validate_meds(filename_meds2) except AttributeError: print('Seems to be the wrong meds package. Unable to do tests of meds file.') return From 967ed39162d11cf4a12c86e474ec4a15a47cdad1 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 20:36:56 -0400 Subject: [PATCH 65/96] Remove some handling of old astropy versions in AstropyWCS code (#755) --- galsim/fitswcs.py | 143 ++++++---------------------------------------- tests/test_wcs.py | 20 ++++++- 2 files changed, 35 insertions(+), 128 deletions(-) diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index d9131efd5af..716e96772a6 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -138,21 +138,20 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if file_name is not None: header = hdu.header - # At least as late as version 2.0.4, astropy thinks it knows how to parse ZPX files, - # but can at least sometimes seg fault when it tries to parse the header. Check for - # that explicitly here and raise an exception before getting to _load_from_header - # TODO: If they ever fix this bug, use the correct version here. - if (astropy.__version__ < '999' and header is not None and - 'CTYPE1' in header and 'ZPX' in header['CTYPE1'].upper()): - raise GalSimError("AstropyWCS cannot (always) parse ZPX files") - # Load the wcs from the header. if header is not None: if wcs is not None: raise GalSimIncompatibleValuesError( "Cannot provide both pyfits header and wcs", header=header, wcs=wcs) self.header = fits.FitsHeader(header) - wcs = self._load_from_header(self.header) + try: + wcs = self._load_from_header(self.header) + except (TypeError, AttributeError, ValueError) as e: + # When parsing ZPX files, astropy raises a very unhelpful error message. + # Ignore that (ValueError in that case, but ignore any similarly mundane error) + # and turn it into a more appropriate OSError. + raise OSError("Astropy failed to read WCS from %s. Original error: %s"%( + file_name, e)) else: self.header = None @@ -165,20 +164,6 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= if file_name is not None: fits.closeHDUList(hdu_list, fin) - # If astropy.wcs cannot parse the header, it won't notice from just doing the - # WCS(header) command. It will silently move on, thinking things are fine until - # later when if will fail (with `RuntimeError: NULL error object in wcslib`). - # We'd rather get that to happen now rather than later. - try: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - ra, dec = wcs.all_pix2world( [ [0, 0] ], 1)[0] - except KeyboardInterrupt: - raise - except Exception as err: - raise OSError("AstropyWCS was unable to read the WCS specification in the header." - "Caught error: %r"%err) - if not wcs.is_celestial: raise GalSimError("The WCS read in does not define a pair of celestial axes" ) self._wcs = wcs @@ -186,19 +171,13 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= def _load_from_header(self, header): import astropy.wcs from . import fits - self._fix_header(header) with warnings.catch_warnings(): # The constructor might emit warnings if it wants to fix the header # information (e.g. RADECSYS -> RADESYSa). We'd rather ignore these # warnings, since we don't much care if the input file is non-standard # so long as we can make it work. warnings.simplefilter("ignore") - # Some versions of astropy don't like to accept a galsim.FitsHeader object - # as the header attribute here, even though they claim that dict-like objects - # are ok. So pull out the astropy.io.header object in this case. - if isinstance(header,fits.FitsHeader): - header = header.header - wcs = astropy.wcs.WCS(header) + wcs = astropy.wcs.WCS(header.header) return wcs @property @@ -207,41 +186,11 @@ def wcs(self): return self._wcs @property def origin(self): return self._origin - def _fix_header(self, header): - # We allow for the option to fix up the header information when a modification can - # make it readable by astropy.wcs. - - # Older versions of astropy had trouble with files where the axes were swapped. - # So fix them if necessary. I know >= 1.0.1 works. 0.2.4 and 0.3.1 both fail. - import astropy - if astropy.__version__ < '1.0.1': # pragma: no cover - ctype1 = header.get('CTYPE1', 'RA---') - ctype2 = header.get('CTYPE2', 'DEC--') - if ctype1.startswith('DEC--') and ctype2.startswith('RA---'): - for key1, key2 in [ ('CTYPE1', 'CTYPE2'), - ('CRVAL1', 'CRVAL2'), - ('CDELT1', 'CDELT2'), - ('CD1_1', 'CD2_1'), - ('CD1_2', 'CD2_2'), - ('PC1_1', 'PC2_1'), - ('PC1_2', 'PC2_2'), - ('CUNIT1', 'CUNIT2') ]: - if key1 in header and key2 in header: - header[key1], header[key2] = header[key2], header[key1] - def _radec(self, x, y, color=None): x1 = np.atleast_1d(x) y1 = np.atleast_1d(y) - try: - # Old versions fail with an AttributeError about astropy.wcs.Wcsprm.lattype - # cf. https://github.com/astropy/astropy/pull/1463 - # This has been fixed for a while now, but leave in this workaround for old versions. - ra, dec = self.wcs.all_pix2world(x1, y1, 1, ra_dec_order=True) - except AttributeError: # pragma: no cover - # If that failed, then we should be on version < 1.0.1, and the header should have - # been fixed above by _fix_header. So this should work correctly. - ra, dec = self.wcs.all_pix2world(x1, y1, 1) + ra, dec = self.wcs.all_pix2world(x1, y1, 1, ra_dec_order=True) # astropy outputs ra, dec in degrees. Need to convert to radians. factor = degrees / radians @@ -263,60 +212,9 @@ def _xy(self, ra, dec, color=None): import astropy factor = radians / degrees rd = np.atleast_2d([ra, dec]) * factor - # Here we have to work around another astropy.wcs bug. The way they use scipy's - # Broyden's method doesn't work. So I implement a fix here. - if astropy.__version__ >= '1.0.1': - # This works now on recent vesions of astropy. At least >= 1.0.1, but possibly - # 1.0 also included the fix. - # cf. https://github.com/astropy/astropy/issues/1977 - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - xy = self.wcs.all_world2pix(rd, 1, ra_dec_order=True)[0] - else: # pragma: no cover - # This section is basically a copy of astropy.wcs's _all_world2pix function, but - # simplified a bit to remove some features we don't need, and with corrections - # to make it work correctly. - import astropy.wcs - import scipy.optimize - - origin = 1 - tolerance = 1.e-6 - - # This call emits a RuntimeWarning about: - # [...]/site-packages/scipy/optimize/nonlin.py:943: RuntimeWarning: invalid value encountered in divide - # d = v / vdot(df, v) - # It seems to be harmless, so we explicitly ignore it here: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - xy0 = self.wcs.wcs_world2pix(rd, origin) - - # Note that the fmod bit accounts for the possibility that ra and the ra returned - # from all_pix2world have a different wrapping around 360. We fmod dec too even - # though it won't do anything, since that's how the numpy array fmod2 has to work. - func = lambda pix: ( - (np.fmod(self.wcs.all_pix2world(np.atleast_2d(pix),origin) - - rd + 180,360) - 180).ravel() ) - - # This is the main bit that the astropy function is missing. - # The scipy.optimize.broyden1 function can't handle starting at exactly the right - # solution. It iterates to its limit and then ends with - # Traceback (most recent call last): - # [... snip ...] - # File "[...]/site-packages/scipy/optimize/nonlin.py", line 331, in nonlin_solve - # raise NoConvergence(_array_like(x, x0)) - # scipy.optimize.nonlin.NoConvergence: [ 113.74961526 179.99982209] - # - # Providing a good estimate of the scale size gets rid of this. And even if we aren't - # starting at exactly the right value, it is hugely more efficient to give it an - # estimate of alpha, since it is not typically near unity in this case, so it is much - # faster to start with something closer to the right value. - alpha = np.mean(np.abs(self.wcs.wcs.get_cdelt())) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - xy = [ scipy.optimize.broyden1(func, xy_init, x_tol=tolerance, alpha=alpha) - for xy_init in xy0 ] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + xy = self.wcs.all_world2pix(rd, 1, ra_dec_order=True)[0] x, y = xy return x, y @@ -1653,16 +1551,11 @@ def TanWCS(affine, world_origin, units=arcsec): PyAstWCS, # This requires `import starlink.Ast` to succeed. This handles the largest # number of WCS types of any of these. In fact, it worked for every one - # we tried in our unit tests (which was not exhaustive). This is a bit - # slower than Astropy, but I think mostly due to their initial reading of - # the fits header -- that seems to take a lot of time for some reason. - # Once it is loaded, the actual usage seems to be quite fast. - - AstropyWCS, # This requires `import astropy.wcs` to succeed. So far, they only handle - # the standard official WCS types. So not TPV, for instance. Also, it is - # a little faster than PyAst, so we prefer PyAst when it is available. - # (But only because of our fix in the _xy function to not use the astropy - # version of all_world2pix function!) + # we tried in our unit tests (which was not exhaustive). + + AstropyWCS, # This requires `import astropy.wcs` to succeed. It doesn't support quite as + # many WCS types as PyAst. It's also usually a little slower, so we prefer + # PyAstWCS when it is available. WcsToolsWCS, # This requires the wcstool command line functions to be installed. # It is very slow, so it should only be used as a last resort. diff --git a/tests/test_wcs.py b/tests/test_wcs.py index 62a9bc77830..122c491ebd0 100644 --- a/tests/test_wcs.py +++ b/tests/test_wcs.py @@ -1775,6 +1775,11 @@ def test_astropywcs(): do_ref(wcs, ref_list, 'AstropyWCS '+tag) + if tag == 'TAN': + # Also check origin. (Now that reference checks are done.) + wcs = galsim.AstropyWCS(file_name, dir=dir, compression='none', hdu=0, + origin=galsim.PositionD(3,4)) + do_celestial_wcs(wcs, 'Astropy file '+file_name) do_wcs_image(wcs, 'AstropyWCS_'+tag) @@ -1796,15 +1801,24 @@ def test_astropywcs(): with assert_raises(galsim.GalSimError): galsim.AstropyWCS('SBProfile_comparison_images/kolmogorov.fits') + # This file does not have any WCS information in it. + with assert_raises(galsim.GalSimError): + galsim.AstropyWCS('fits_files/blankimg.fits') + assert_raises(TypeError, galsim.AstropyWCS) assert_raises(TypeError, galsim.AstropyWCS, file_name, header='dummy') assert_raises(TypeError, galsim.AstropyWCS, file_name, wcs=wcs) assert_raises(TypeError, galsim.AstropyWCS, wcs=wcs, header='dummy') # Astropy thinks it can handle ZPX files, but as of version 2.0.4, they don't work right. - # Check that we raise an exception for any attempt to read these with astropy. - # (And if they ever fix this, add 'ZPX' to the test_tags above and remvoe this check.) - assert_raises(galsim.GalSimError, galsim.AstropyWCS, references['ZPX'][0], dir=dir) + # It reads it in ok, and even works with it fine. But it doesn't round trip through + # its own write and read. Even worse, it natively gives a fairly obscure error, which + # we convert into an OSError by hand. + # This test will let us know when they finally fix it. If it fails, we can remove this + # test and add 'ZPX' to the list of working astropy.wcs types above. + with assert_raises(OSError): + wcs = galsim.AstropyWCS(references['ZPX'][0], dir=dir) + do_wcs_image(wcs, 'AstropyWCS_ZPX') @timer def test_pyastwcs(): From 8137eac8f1a2b9cc44101e5574e7233b41f0121c Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 3 May 2018 20:39:02 -0400 Subject: [PATCH 66/96] Various improvements to test suite coverage (#755) --- galsim/bounds.py | 7 +- galsim/catalog.py | 12 +-- galsim/chromatic.py | 11 ++- galsim/des/des_psfex.py | 22 ++--- galsim/fits.py | 9 +- galsim/fitswcs.py | 13 +-- galsim/gsobject.py | 5 -- galsim/interpolatedimage.py | 2 +- galsim/position.py | 2 - galsim/random.py | 7 +- galsim/real.py | 2 - galsim/scene.py | 12 +-- galsim/sensor.py | 6 +- galsim/wfirst/wfirst_wcs.py | 2 +- tests/test_catalog.py | 72 ++++++++-------- tests/test_chromatic.py | 11 ++- tests/test_config_image.py | 19 ++--- tests/test_config_noise.py | 6 ++ tests/test_convolve.py | 3 + tests/test_correlatednoise.py | 8 +- tests/test_detectors.py | 16 ++-- tests/test_hsm.py | 3 + tests/test_image.py | 143 +++++++++++++++++++++++++++++--- tests/test_interpolatedimage.py | 2 + tests/test_phase_psf.py | 2 +- tests/test_random.py | 5 ++ tests/test_real.py | 27 +++++- tests/test_sed.py | 18 ++-- tests/test_transforms.py | 2 + tests/test_utilities.py | 49 +++++++++-- tests/test_wcs.py | 32 ++++++- tests/test_wfirst.py | 3 +- 32 files changed, 383 insertions(+), 150 deletions(-) diff --git a/galsim/bounds.py b/galsim/bounds.py index 47aa7dfd7f8..a765d761e54 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -129,13 +129,13 @@ def _parse_args(self, *args, **kwargs): self.ymin = min(args[0].y, args[1].y) self.ymax = max(args[0].y, args[1].y) else: - raise TypeError("Two arguments to %s must be either Positions"%( + raise TypeError("Two arguments to %s must be Positions"%( self.__class__.__name__)) else: raise TypeError("%s takes either 1, 2, or 4 arguments (%d given)"%( self.__class__.__name__,len(args))) elif len(args) != 0: - raise TypeError("Cannot provide both keywork and non-keyword arguments to %s"%( + raise TypeError("Cannot provide both keyword and non-keyword arguments to %s"%( self.__class__.__name__)) else: try: @@ -373,9 +373,6 @@ class BoundsD(Bounds): def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) - if (self.xmin != float(self.xmin) or self.xmax != float(self.xmax) or - self.ymin != float(self.ymin) or self.ymax != float(self.ymax)): - raise TypeError("BoundsD must be initialized with float values") self.xmin = float(self.xmin) self.xmax = float(self.xmax) self.ymin = float(self.ymin) diff --git a/galsim/catalog.py b/galsim/catalog.py index 13d2e4f50e0..fdf7291b29f 100644 --- a/galsim/catalog.py +++ b/galsim/catalog.py @@ -122,16 +122,10 @@ def readAscii(self, comments, _nobjects_only=False): # Note: we leave the data as str, rather than convert to float, so that if # we have any str fields, they don't give an error here. They'll only give an # error if one tries to convert them to float at some point. - self.data = np.loadtxt(self.file_name, comments=comments, dtype=bytes) + self.data = np.loadtxt(self.file_name, comments=comments, dtype=bytes, ndmin=2) # Convert the bytes to str. For Py2, this is a no op. self.data = self.data.astype(str) - # If only one row, then the shape comes in as one-d. - if len(self.data.shape) == 1: - self.data = self.data.reshape(1, -1) - if len(self.data.shape) != 2: - raise OSError('Unable to parse the input catalog as a 2-d array') - self.nobjects = self.data.shape[0] self.ncols = self.data.shape[1] self.isfits = False @@ -283,12 +277,10 @@ def __init__(self, file_name, dir=None, file_type=None, key_split='.'): import yaml with open(self.file_name, 'r') as f: self.dict = yaml.load(f) - elif file_type == 'JSON': + else: # JSON import json with open(self.file_name, 'r') as f: self.dict = json.load(f) - else: - raise GalSimValueError("Invalid file_type", file_type, ('Pickle', 'YAML', 'JSON')) def get(self, key, default=None): # Make a list of keys according to our key_split parameter diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 928e572426f..a6da67368e8 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -528,7 +528,7 @@ def evaluateAtWavelength(self, wave): @returns the monochromatic object at the given wavelength. """ - if self.__class__ != ChromaticObject: + if self.__class__ != ChromaticObject: # pragma: no cover raise NotImplementedError( "Subclasses of ChromaticObject must override evaluateAtWavelength()") return self._obj.evaluateAtWavelength(wave) @@ -2397,6 +2397,15 @@ def __init__(self, obj, **kwargs): self.SED = SED(lambda w:math.sqrt(obj.SED(w)), 'nm', '1') self.wave_list = obj.wave_list + def __eq__(self, other): + return (isinstance(other, ChromaticFourierSqrtProfile) and + self._obj == other._obj and + self.kwargs == other.kwargs) + + def __hash__(self): + return hash(("galsim.ChromaticFourierSqrtProfile", self._obj, + frozenset(self.kwargs.items()))) + def __repr__(self): kwargs_str = ', '.join('%s=%s'%(k,v) for k,v in self.kwargs.items()) return 'galsim.ChromaticFourierSqrtProfile(%r, %s)'%(self._obj, kwargs_str) diff --git a/galsim/des/des_psfex.py b/galsim/des/des_psfex.py index 414b59ce9ac..c87a3280d35 100644 --- a/galsim/des/des_psfex.py +++ b/galsim/des/des_psfex.py @@ -208,27 +208,27 @@ def read(self): hdu_list.close() # Check for valid values of all these things. - if pol_naxis != 2: + if pol_naxis != 2: # pragma: no cover raise OSError("PSFEx: Expected POLNAXIS == 2, got %d"%pol_naxis) - if not (pol_name1.startswith('X') and pol_name1.endswith('IMAGE')): + if not (pol_name1.startswith('X') and pol_name1.endswith('IMAGE')): # pragma: no cover raise OSError("PSFEx: Expected POLNAME1 == X*_IMAGE, got %s"%pol_name1) - if not (pol_name2.startswith('Y') and pol_name2.endswith('IMAGE')): + if not (pol_name2.startswith('Y') and pol_name2.endswith('IMAGE')): # pragma: no cover raise OSError("PSFEx: Expected POLNAME2 == Y*_IMAGE, got %s"%pol_name2) - if pol_ngrp != 1: + if pol_ngrp != 1: # pragma: no cover raise OSError("PSFEx: Current implementation requires POLNGRP == 1, got %d"%pol_ngrp) - if pol_group1 != 1: + if pol_group1 != 1: # pragma: no cover raise OSError("PSFEx: Expected POLGRP1 == 1, got %s"%pol_group1) - if pol_group2 != 1: + if pol_group2 != 1: # pragma: no cover raise OSError("PSFEx: Expected POLGRP2 == 1, got %s"%pol_group2) - if psf_naxis != 3: + if psf_naxis != 3: # pragma: no cover raise OSError("PSFEx: Expected PSFNAXIS == 3, got %d"%psf_naxis) - if psf_axis3 != ((pol_deg+1)*(pol_deg+2))//2: + if psf_axis3 != ((pol_deg+1)*(pol_deg+2))//2: # pragma: no cover raise OSError("PSFEx: POLDEG and PSFAXIS3 disagree") - if basis.shape[0] != psf_axis3: + if basis.shape[0] != psf_axis3: # pragma: no cover raise OSError("PSFEx: PSFAXIS3 disagrees with actual basis size") - if basis.shape[1] != psf_axis2: + if basis.shape[1] != psf_axis2: # pragma: no cover raise OSError("PSFEx: PSFAXIS2 disagrees with actual basis size") - if basis.shape[2] != psf_axis1: + if basis.shape[2] != psf_axis1: # pragma: no cover raise OSError("PSFEx: PSFAXIS1 disagrees with actual basis size") # Save some of these values for use in building the interpolated images diff --git a/galsim/fits.py b/galsim/fits.py index 4855b58e018..3afc21704ca 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -53,7 +53,8 @@ def _parse_compression(compression, file_name): if file_name.lower().endswith('.fz'): pyfits_compress = 'RICE_1' elif file_name.lower().endswith('.gz'): file_compress = 'gzip' elif file_name.lower().endswith('.bz2'): file_compress = 'bzip2' - else: pass + else: # pragma: no cover (Not sure why Travis thinks this isn't covered.) + pass else: raise GalSimValueError("Invalid compression", compression, ('rice', 'gzip_tile', 'hcompress', 'plio', 'gzip', 'bzip2', @@ -724,6 +725,9 @@ def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto'): try: hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + if hdu.data is None: + raise OSError("HDU is empty. (data is None)") + wcs, origin = wcs.readFromFitsHeader(hdu.header) dt = hdu.data.dtype.type if dt in Image.valid_dtypes: @@ -876,6 +880,9 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut try: hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + if hdu.data is None: + raise OSError("HDU is empty. (data is None)") + wcs, origin = wcs.readFromFitsHeader(hdu.header) dt = hdu.data.dtype.type if dt in Image.valid_dtypes: diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 716e96772a6..cdc43a1ad92 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -546,6 +546,8 @@ def __setstate__(self, d): # I can't figure out how to get wcstools installed in the travis environment (cf. .travis.yml). # So until that gets resolved, we omit this class from the coverage report. +# This class was mostly useful as a refernce implementation anyway. It's much too slow for most +# users to ever want to use it. class WcsToolsWCS(CelestialWCS): # pragma: no cover """This WCS uses wcstools executables to perform the appropriate WCS transformations for a given FITS file. It requires wcstools command line functions to be installed. @@ -592,7 +594,7 @@ def __init__(self, file_name, dir=None, origin=None): stdout=subprocess.PIPE) results = p.communicate()[0] p.stdout.close() - if len(results) == 0: + if len(results) == 0 or 'cannot' in results: raise OSError('wcstools (specifically xy2sky) was unable to read '+file_name) # wcstools supports LINEAR WCS's, but we don't want to allow them, since then @@ -889,8 +891,8 @@ def _read_header(self, header): from .angle import AngleUnit from .celestial import CelestialCoord # Start by reading the basic WCS stuff that most types have. - ctype1 = header['CTYPE1'] - ctype2 = header['CTYPE2'] + ctype1 = header.get('CTYPE1','') + ctype2 = header.get('CTYPE2','') if ctype1.startswith('DEC--') and ctype2.startswith('RA---'): flip = True elif ctype1.startswith('RA---') and ctype2.startswith('DEC--'): @@ -899,7 +901,7 @@ def _read_header(self, header): raise GalSimError( "GSFitsWCS only supports celestial coordinate systems." "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%(ctype1, ctype2)) - if ctype1[5:] != ctype2[5:]: + if ctype1[5:] != ctype2[5:]: # pragma: no cover raise OSError("ctype1, ctype2 do not seem to agree on the WCS type") self.wcs_type = ctype1[5:] if self.wcs_type in ('TAN', 'TPV', 'TNX', 'TAN-SIP'): @@ -1393,8 +1395,7 @@ def _local(self, image_pos, color=None): def _newOrigin(self, origin): ret = self.copy() - if origin is not None: - ret.crpix = ret.crpix + [ origin.x, origin.y ] + ret.crpix = ret.crpix + [ origin.x, origin.y ] return ret def _writeHeader(self, header, bounds): diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 3beca7ee358..8e5c167d8f2 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -2268,11 +2268,6 @@ def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, if nx is not None or ny is not None: raise GalSimIncompatibleValuesError( "Cannot provide nx,ny if image is provided", nx=nx, ny=ny, image=image) - if not image.bounds.isDefined(): - if add_to_image: - raise GalSimIncompatibleValuesError( - "Cannot add_to_image if image bounds are not defined", - add_to_image=add_to_image, image=image) # Can't both recenter a provided image and add to it. if recenter and image.center != PositionI(0,0) and add_to_image: diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index c79ecb23709..fcb60a7d869 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -414,7 +414,7 @@ def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, # as large as we need in any direction. if noise_pad_size: if noise_pad_size < 0: - raise GalSimValuesError("noise_pad_size may not be negative", noise_pad_size) + raise GalSimValueError("noise_pad_size may not be negative", noise_pad_size) if not noise_pad: raise GalSimIncompatibleValuesError( "Must provide noise_pad if noise_pad_size > 0", diff --git a/galsim/position.py b/galsim/position.py index ebfabc46f5b..9c17952c08f 100644 --- a/galsim/position.py +++ b/galsim/position.py @@ -163,8 +163,6 @@ class PositionD(Position): """ def __init__(self, *args, **kwargs): self._parse_args(*args, **kwargs) - if self.x != float(self.x) or self.y != float(self.y): - raise TypeError("PositionD must be initialized with float values") self.x = float(self.x) self.y = float(self.y) diff --git a/galsim/random.py b/galsim/random.py index b8c317f0868..03b1c66889f 100644 --- a/galsim/random.py +++ b/galsim/random.py @@ -712,10 +712,9 @@ def __init__(self, seed=None, function=None, x_min=None, "can eval to a function of x.\n" "Caught error: {0}".format(e), self.__function) else: - self.__function = weakref.ref(function) # Save the inputs to be used in repr # Check that the function is actually a function - if not (isinstance(function, LookupTable) or hasattr(function, '__call__')): - raise TypeError('Keyword function must be a callable function or a string') + if not hasattr(function, '__call__'): + raise TypeError('function must be a callable function or a string') if interpolant: raise GalSimIncompatibleValuesError( "Cannot provide an interpolant with a callable function argument", @@ -734,6 +733,8 @@ def __init__(self, seed=None, function=None, x_min=None, "python callable function", function=function, x_min=x_min, x_max=x_max) + self.__function = weakref.ref(function) # Save the inputs to be used in repr + # Compute the probability distribution function, pdf(x) if (npoints is None and isinstance(function, LookupTable) and not function.x_log and not function.f_log): diff --git a/galsim/real.py b/galsim/real.py index af48c543efc..efe617257a9 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -869,8 +869,6 @@ def _parse_files_dirs(file_name, image_dir, sample): full_file_name = os.path.join(image_dir,file_name) if not os.path.isfile(full_file_name): raise OSError(full_file_name+' not found.') - if not os.path.isdir(image_dir): - raise OSError(image_dir+' directory does not exist.') return full_file_name, image_dir, use_sample diff --git a/galsim/scene.py b/galsim/scene.py index 65de2ea1ad5..b32ea38dbbf 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -309,14 +309,10 @@ def _apply_exclusion(self, exclusion_level, min_hlr=0, max_hlr=0, min_flux=0, ma # Some fit parameters can indicate a likely sky subtraction error: very high sersic n # AND abnormally large half-light radius (>1 arcsec). - if 'hlr' not in self.param_cat.dtype.names: - # This is the circularized HLR in arcsec, which we have to compute from the stored - # parametric fits. - hlr = cosmos_pix_scale * self.param_cat['sersicfit'][:,1] * \ - np.sqrt(self.param_cat['sersicfit'][:,2]) - else: - # This is the pre-computed circularized HLR in arcsec. - hlr = self.param_cat['hlr'][:,0] + if 'hlr' not in self.param_cat.dtype.names: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + hlr = self.param_cat['hlr'][:,0] n = self.param_cat['sersicfit'][:,2] mask &= ( (n < 5) | (hlr < 1.) ) diff --git a/galsim/sensor.py b/galsim/sensor.py index 73d29c7ca28..c085865d224 100644 --- a/galsim/sensor.py +++ b/galsim/sensor.py @@ -172,7 +172,7 @@ def __init__(self, name='lsst_itl_8', strength=1.0, rng=None, diffusion_factor=1 raise OSError("Cannot locate file %s or %s"%(self.config_file, cfg_file)) self.config_file = cfg_file self.vertex_file = os.path.join(meta_data.share_dir, 'sensors', self.vertex_file) - if not os.path.isfile(self.vertex_file): + if not os.path.isfile(self.vertex_file): # pragma: no cover raise OSError("Cannot locate vertex file %s"%(self.vertex_file)) self.config = self._read_config_file(self.config_file) @@ -205,7 +205,7 @@ def _init_silicon(self): nrecalc = float(self.nrecalc) / self.strength vertex_data = np.loadtxt(self.vertex_file, skiprows = 1) - if vertex_data.shape != (Nx * Ny * (4 * NumVertices + 4), 5): + if vertex_data.shape != (Nx * Ny * (4 * NumVertices + 4), 5): # pragma: no cover raise OSError("Vertex file %s does not match config file %s"%( self.vertex_file, self.config_file)) @@ -317,7 +317,7 @@ def _read_config_file(self, filename): lines=file.readlines() lines = [ l.strip() for l in lines ] lines = [ l.split() for l in lines if len(l) > 0 and l[0] != '#' ] - if any([l[1] != '=' for l in lines]): + if any([l[1] != '=' for l in lines]): # pragma: no cover raise OSError("Error reading config file %s"%filename) config = dict([(l[0], l[2]) for l in lines]) # convert strings to int or float values when appropriate diff --git a/galsim/wfirst/wfirst_wcs.py b/galsim/wfirst/wfirst_wcs.py index 170f4e90b70..12e18ffea78 100644 --- a/galsim/wfirst/wfirst_wcs.py +++ b/galsim/wfirst/wfirst_wcs.py @@ -446,7 +446,7 @@ def _populate_required_fields(header): ('INSTRUME', instr_name, "identifier for instrument used to acquire data"), ]) -def _parse_sip_file(file): +def _parse_sip_file(file): # pragma: no cover """ Utility routine to parse the file with the SIP coefficients and hand back some arrays to be used for later calculations. diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 575228d590c..c8a6ed92f7d 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -41,33 +41,20 @@ def test_ascii_catalog(): cat2 = galsim.Catalog('catalog.txt', 'config_input', comments='#', file_type='ASCII') assert cat2 == cat + assert len(cat2) == cat2.nobjects == cat2.getNObjects() == cat.nobjects + assert cat2.ncols == cat.ncols + # Special _nobjects_only option sets nobjects, but doesn't finish setting up object. cat3 = galsim.Catalog('catalog.txt', 'config_input', _nobjects_only=True) assert cat3 == cat - assert cat3.nobjects == cat3.getNObjects() == cat.nobjects + assert len(cat3) == cat3.nobjects == cat3.getNObjects() == cat.nobjects with assert_raises(AttributeError): assert cat3.ncols with assert_raises(AttributeError): - cat3.get(1,11) - - with assert_raises(galsim.GalSimValueError): - galsim.Catalog('catalog.txt', 'config_input', file_type='invalid') - - with assert_raises(IndexError): - cat.get(-1, 11) - with assert_raises(IndexError): - cat.get(3, 11) - with assert_raises(IndexError): - cat.get(1, -1) - with assert_raises(IndexError): - cat.get(1, 50) - with assert_raises(IndexError): - cat.get('val', 11) - with assert_raises(IndexError): - cat.get(3, 'val') + assert cat3.get(1,11) cat2 = galsim.Catalog('catalog2.txt', 'config_input', comments='%') - assert len(cat2) == cat2.nobjects == cat.nobjects + assert cat2.nobjects == cat.nobjects np.testing.assert_array_equal(cat2.data, cat.data) assert cat2 != cat do_pickle(cat2) @@ -81,8 +68,19 @@ def test_ascii_catalog(): cat3n = galsim.Catalog('catalog3.txt', 'config_input', comments=None, _nobjects_only=True) assert cat3n.nobjects == 3 - with assert_raises((IOError, OSError)): - galsim.Catalog('invalid.txt', 'config_input') + # Check construction errors + assert_raises(galsim.GalSimValueError, galsim.Catalog, 'catalog.txt', file_type='invalid') + assert_raises(ValueError, galsim.Catalog, 'catalog3.txt', 'config_input', comments="#%") + assert_raises((IOError, OSError), galsim.Catalog, 'catalog.txt') # Wrong dir + assert_raises((IOError, OSError), galsim.Catalog, 'invalid.txt', 'config_input') + + # Check indexing errors + assert_raises(IndexError, cat.get, -1, 11) + assert_raises(IndexError, cat.get, 3, 11) + assert_raises(IndexError, cat.get, 1, -1) + assert_raises(IndexError, cat.get, 1, 50) + assert_raises(IndexError, cat.get, 'val', 11) + assert_raises(IndexError, cat.get, 3, 'val') @timer @@ -101,29 +99,31 @@ def test_fits_catalog(): cat2 = galsim.Catalog('catalog.fits', 'config_input', hdu=1, file_type='FITS') assert cat2 == cat + assert len(cat2) == cat2.nobjects == cat2.getNObjects() == cat.nobjects + assert cat2.ncols == cat.ncols + # Special _nobjects_only option sets nobjects, but doesn't finish setting up object. cat3 = galsim.Catalog('catalog.fits', 'config_input', _nobjects_only=True) assert cat3 == cat assert len(cat3) == cat3.nobjects == cat3.getNObjects() == cat.nobjects with assert_raises(AttributeError): assert cat3.ncols with assert_raises(AttributeError): - cat3.get(1,'angle2') + cat3.get(1, 'angle2') - with assert_raises(galsim.GalSimValueError): - galsim.Catalog('catalog.fita', 'config_input', file_type='invalid') + # Check construction errors + assert_raises(galsim.GalSimValueError, galsim.Catalog, 'catalog.fits', file_type='invalid') + assert_raises((IOError, OSError), galsim.Catalog, 'catalog.fits') # Wrong dir + assert_raises((IOError, OSError), galsim.Catalog, 'invalid.fits', 'config_input') - with assert_raises(IndexError): - cat.get(-1, 'angle2') - with assert_raises(IndexError): - cat.get(3, 'angle2') - with assert_raises(KeyError): - cat.get(1, 'invalid') - with assert_raises(KeyError): - cat.get(1, 3) - with assert_raises(IndexError): - cat.get('val', 'angle2') + # Check indexing errors + assert_raises(IndexError, cat.get, -1, 'angle2') + assert_raises(IndexError, cat.get, 3, 'angle2') + assert_raises(KeyError, cat.get, 1, 'invalid') + assert_raises(KeyError, cat.get, 1, 3) + assert_raises(IndexError, cat.get, 'val', 'angle2') + # Check non-default hdu cat2 = galsim.Catalog('catalog2.fits', 'config_input', hdu=2) assert len(cat2) == cat2.nobjects == cat.nobjects np.testing.assert_array_equal(cat2.data, cat.data) @@ -134,14 +134,12 @@ def test_fits_catalog(): assert cat3.nobjects == cat.nobjects np.testing.assert_array_equal(cat3.data, cat.data) assert cat3 != cat - assert cat3 != cat2 # Even though these are the same, it doesn't no 'data' is hdu 2. + assert cat3 != cat2 # Even though these are the same, it doesn't know 'data' is hdu 2. do_pickle(cat3) cat2n = galsim.Catalog('catalog2.fits', 'config_input', hdu=2, _nobjects_only=True) assert cat2n.nobjects == 3 - with assert_raises((IOError, OSError)): - galsim.Catalog('invalid.fits', 'config_input') @timer diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index e7de53f7e18..60c8c36c457 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -2319,20 +2319,27 @@ def test_ne(): galsim.ChromaticDeconvolution(cgal1, gsparams=gsp)] all_obj_diff(gals) - # ChromaticAutoConvolution. Only params here are obj to deconvolve and gsparams. + # ChromaticAutoConvolution. gals = [galsim.ChromaticAutoConvolution(cgal1), galsim.ChromaticAutoConvolution(cgal2), galsim.ChromaticAutoConvolution(cgal3), galsim.ChromaticAutoConvolution(cgal1, gsparams=gsp)] all_obj_diff(gals) - # ChromaticAutoCorrelation. Only params here are obj to deconvolve and gsparams. + # ChromaticAutoCorrelation. gals = [galsim.ChromaticAutoCorrelation(cgal1), galsim.ChromaticAutoCorrelation(cgal2), galsim.ChromaticAutoCorrelation(cgal3), galsim.ChromaticAutoCorrelation(cgal1, gsparams=gsp)] all_obj_diff(gals) + # ChromaticFourierSqrt. + gals = [galsim.ChromaticFourierSqrtProfile(cgal1), + galsim.ChromaticFourierSqrtProfile(cgal2), + galsim.ChromaticFourierSqrtProfile(cgal3), + galsim.ChromaticFourierSqrtProfile(cgal1, gsparams=gsp)] + all_obj_diff(gals) + # ChromaticOpticalPSF. Params include: lam, (diam or lam_over_diam), aberrations, nstruts, # strut_thick, strut_angle, obscuration, oversampling, pad_factor, flux, gsparams, ... # Most of these get tested in the same way, (via a kwargs dict comparison), so only test a few diff --git a/tests/test_config_image.py b/tests/test_config_image.py index 1b65e083bd8..f319e4b5101 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1591,18 +1591,13 @@ def test_wcs(): wcs = galsim.config.BuildWCS(config, 'wcs', config) assert wcs == galsim.PixelScale(1.0) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad1', config) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad2', config) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad3', config) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad4', config) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad5', config) - with assert_raises(galsim.GalSimConfigError): - galsim.config.BuildWCS(config['image'], 'bad6', config) + for bad in ['bad1', 'bad2', 'bad3', 'bad4', 'bad5', 'bad6']: + with assert_raises(galsim.GalSimConfigError): + galsim.config.BuildWCS(config['image'], bad, config) + + # Base class usage is invalid + builder = galsim.config.wcs.WCSBuilder() + assert_raises(NotImplementedError, builder.buildWCS, config, config, logger=None) @timer def test_index_key(): diff --git a/tests/test_config_noise.py b/tests/test_config_noise.py index 8974971f683..4a17f95d6f5 100644 --- a/tests/test_config_noise.py +++ b/tests/test_config_noise.py @@ -85,6 +85,12 @@ def test_gaussian(): im1c = galsim.config.BuildImage(config) np.testing.assert_equal(im1c.array, im1a.array) + # Base class usage is invalid + builder = galsim.config.noise.NoiseBuilder() + assert_raises(NotImplementedError, builder.addNoise, config, config, im1a, rng, var, + draw_method='auto', logger=None) + assert_raises(NotImplementedError, builder.getNoiseVariance, config, config) + @timer def test_poisson(): diff --git a/tests/test_convolve.py b/tests/test_convolve.py index fac7ea71264..fc3fa2a4906 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -335,6 +335,7 @@ def test_realspace_convolve(): # non-analytic profiles cannot do real_space d = galsim.Deconvolve(galsim.Gaussian(sigma=2)) assert_warns(galsim.GalSimWarning, galsim.Convolve, [g, d], real_space=True) + assert_raises(TypeError, galsim.Convolve, [g, d], real_space='true') # Repeat some of the above for AutoConvolve and AutoCorrelate conv = galsim.AutoConvolve(psf,real_space=True) @@ -351,6 +352,8 @@ def test_realspace_convolve(): assert_warns(galsim.GalSimWarning, galsim.AutoConvolve, d, real_space=True) assert_warns(galsim.GalSimWarning, galsim.AutoCorrelate, psf, real_space=False) assert_warns(galsim.GalSimWarning, galsim.AutoCorrelate, d, real_space=True) + assert_raises(TypeError, galsim.AutoConvolve, d, real_space='true') + assert_raises(TypeError, galsim.AutoCorrelate, d, real_space='true') @timer diff --git a/tests/test_correlatednoise.py b/tests/test_correlatednoise.py index 76cc97ed211..cff81ad4253 100644 --- a/tests/test_correlatednoise.py +++ b/tests/test_correlatednoise.py @@ -845,8 +845,7 @@ def test_symmetrizing(): outimage = galsim.ImageD(symm_size_mult * largeim_size, symm_size_mult * largeim_size, scale=cosmos_scale) outimage.addNoise(ccn) # Add the COSMOS noise - # Then estimate correlation function from generated noise - cntest_correlated = galsim.CorrelatedNoise(outimage, ccn.rng) + outimage2 = outimage.copy() # Now apply 4-fold symmetry to the noise field, and check that its variance and covariances are # as expected (non-zero distance correlations should be symmetric) symmetrized_variance = ccn.symmetrizeImage(outimage, order=4) @@ -871,6 +870,11 @@ def test_symmetrizing(): err_msg="Noise field generated by symmetrizing COSMOS CorrelatedNoise does not have "+ "approximate 4-fold symmetry") + # If outimage doesn't have a scale set, then it uses the ccn scale. + outimage2.wcs = None + symmetrized_variance2 = ccn.symmetrizeImage(outimage2, order=4) + np.testing.assert_almost_equal(symmetrized_variance2, symmetrized_variance) + # Now test symmetrizing, but having first expanded and sheared the COSMOS noise correlation. # Also we'll make the output image odd-sized, so as to ensure we test that option. ccn_transformed = ccn.shear(g1=-0.05, g2=0.11).rotate(313. * galsim.degrees).expand(2.1) diff --git a/tests/test_detectors.py b/tests/test_detectors.py index b4b5252935e..6804cdc5487 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -353,19 +353,25 @@ def test_IPC_basic(): # Testing for flux conservation np.random.seed(1234) ipc_kernel = galsim.Image(abs(np.random.randn(3,3))) # a random kernel - ipc_kernel /= ipc_kernel.array.sum() # but make it normalized so we do not get warnings im_new = im.copy() # Set edges to zero since flux is not conserved at the edges otherwise im_new.array[0,:] = 0.0 im_new.array[-1,:] = 0.0 im_new.array[:,0] = 0.0 im_new.array[:,-1] = 0.0 - im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='extend', kernel_normalization=True) + with assert_warns(galsim.GalSimWarning): # warn about the sum not being 1 + im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='extend') np.testing.assert_almost_equal(im_new.array.sum(), im.array[1:-1,1:-1].sum(), 4, err_msg="Normalized IPC kernel does not conserve the total flux for 'extend' option.") + # With kernel_normalization = False, it won't warn, but it also won't conserve flux. im_new = im.copy() - im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='wrap', kernel_normalization=True) + im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='extend', kernel_normalization=False) + assert np.abs(im_new.array.sum() - im.array[1:-1,1:-1].sum()) > 1.e-8 + + im_new = im.copy() + ipc_kernel /= ipc_kernel.array.sum() # Explicitly normalizing also avoids warning. + im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='wrap') np.testing.assert_almost_equal(im_new.array.sum(), im.array.sum(), 4, err_msg="Normalized IPC kernel does not conserve the total flux for 'wrap' option.") @@ -375,7 +381,7 @@ def test_IPC_basic(): ipc_kernel.setValue(2,3,0.125) # This kernel should correspond to each pixel getting contribution from the pixel above it. im1 = im.copy() - im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop',kernel_normalization=False) + im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop') np.testing.assert_array_almost_equal(0.875*im.array[1:-1,1:-1]+0.125*im.array[2:,1:-1], im1.array[1:-1,1:-1], 7, err_msg="Difference in directionality for up kernel in applyIPC") # Checking for one pixel in the central bulk @@ -387,7 +393,7 @@ def test_IPC_basic(): ipc_kernel.setValue(1,2,0.125) # This kernel should correspond to each pixel getting contribution from the pixel to its left. im1 = im.copy() - im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop',kernel_normalization=False) + im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop') np.testing.assert_array_almost_equal(im1.array[1:-1,1:-1], im1.array[1:-1,1:-1], 7, err_msg="Difference in directionality for left kernel in applyIPC") # Checking for one pixel in the central bulk diff --git a/tests/test_hsm.py b/tests/test_hsm.py index 69788973f6f..2a067c99a26 100644 --- a/tests/test_hsm.py +++ b/tests/test_hsm.py @@ -570,6 +570,9 @@ def test_hsmparams(): assert_raises(galsim.GalSimError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, hsmparams=new_params_size) + assert_raises(TypeError, galsim.hsm.EstimateShear, tot_gal_image, tot_psf_image, + hsmparams='hsmparams') + @timer def test_hsmparams_nodefault(): diff --git a/tests/test_image.py b/tests/test_image.py index 42c5b03805d..da5440c95dc 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -108,6 +108,7 @@ def test_Image_basic(): assert im1.array.dtype.type == np_array_type assert im1.array.flags.writeable == True assert im1.array.flags.c_contiguous == True + assert im1.dtype == np_array_type im1.fill(23) np.testing.assert_array_equal(im1.array, 23.) @@ -154,6 +155,7 @@ def test_Image_basic(): assert im2_view.ymax == nrow assert im2_view.bounds == bounds assert im2_view.array.dtype.type == np_array_type + assert im2_view.dtype == np_array_type assert im2_cview.xmin == 1 assert im2_cview.xmax == ncol @@ -161,6 +163,7 @@ def test_Image_basic(): assert im2_cview.ymax == nrow assert im2_cview.bounds == bounds assert im2_cview.array.dtype.type == np_array_type + assert im2_cview.dtype == np_array_type assert im1.real.bounds == bounds assert im1.imag.bounds == bounds @@ -171,14 +174,14 @@ def test_Image_basic(): assert im2_cview.real.bounds == bounds assert im2_cview.imag.bounds == bounds if tchar[i] == 'CF': - assert im1.real.array.dtype.type == np.float32 - assert im1.imag.array.dtype.type == np.float32 + assert im1.real.dtype == np.float32 + assert im1.imag.dtype == np.float32 elif tchar[i] == 'CD': - assert im1.real.array.dtype.type == np.float64 - assert im1.imag.array.dtype.type == np.float64 + assert im1.real.dtype == np.float64 + assert im1.imag.dtype == np.float64 else: - assert im1.real.array.dtype.type == np_array_type - assert im1.imag.array.dtype.type == np_array_type + assert im1.real.dtype == np_array_type + assert im1.imag.dtype == np_array_type # Check various ways to set and get values for y in range(1,nrow+1): @@ -506,6 +509,9 @@ def test_Image_FITS_IO(): assert_raises(TypeError, ref_image.write) assert_raises(TypeError, ref_image.write, file_name=test_file, hdu_list=hdu) + # If clobbert = False, then trying to overwrite will raise an OSError + assert_raises(OSError, ref_image.write, test_file, clobber=False) + # # Test various compression schemes # @@ -516,6 +522,8 @@ def test_Image_FITS_IO(): if i > 0 and __name__ != "__main__": continue + test_file0 = test_file # Save the name of the uncompressed file. + # Test full-file gzip test_file = os.path.join(datadir, "test"+tchar[i]+".fits.gz") test_image = galsim.fits.read(test_file, compression='gzip') @@ -537,6 +545,13 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for auto full-file gzip") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image = galsim.fits.read(test_file, compression=None) + np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, + err_msg="Image"+tchar[i]+" write failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.read, test_file0, compression='gzip') + # Test full-file bzip2 test_file = os.path.join(datadir, "test"+tchar[i]+".fits.bz2") test_image = galsim.fits.read(test_file, compression='bzip2') @@ -558,6 +573,13 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for auto full-file bzip2") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image = galsim.fits.read(test_file, compression=None) + np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, + err_msg="Image"+tchar[i]+" write failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.read, test_file0, compression='bzip2') + # Test rice test_file = os.path.join(datadir, "test"+tchar[i]+".fits.fz") test_image = galsim.fits.read(test_file, compression='rice') @@ -579,6 +601,9 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for auto rice") + assert_raises(OSError, galsim.fits.read, test_file0, compression='rice') + assert_raises(OSError, galsim.fits.read, test_file, compression='none') + # Test gzip_tile test_file = os.path.join(datadir, "test"+tchar[i]+"_internal.fits.gzt") ref_image.write(test_file, compression='gzip_tile') @@ -586,6 +611,9 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for gzip_tile") + assert_raises(OSError, galsim.fits.read, test_file0, compression='gzip_tile') + assert_raises(OSError, galsim.fits.read, test_file, compression='none') + # Test hcompress test_file = os.path.join(datadir, "test"+tchar[i]+"_internal.fits.hc") ref_image.write(test_file, compression='hcompress') @@ -593,6 +621,9 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for hcompress") + assert_raises(OSError, galsim.fits.read, test_file0, compression='hcompress') + assert_raises(OSError, galsim.fits.read, test_file, compression='none') + # Test plio (only valid on positive integer values) if tchar[i] in ['S', 'I']: test_file = os.path.join(datadir, "test"+tchar[i]+"_internal.fits.plio") @@ -601,6 +632,9 @@ def test_Image_FITS_IO(): np.testing.assert_array_equal(ref_array.astype(types[i]), test_image.array, err_msg="Image"+tchar[i]+" write failed for plio") + assert_raises(OSError, galsim.fits.read, test_file0, compression='plio') + assert_raises(OSError, galsim.fits.read, test_file, compression='none') + @timer def test_Image_MultiFITS_IO(): @@ -724,6 +758,8 @@ def test_Image_MultiFITS_IO(): compression='invalid') assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='rice') assert_raises(OSError, galsim.fits.readFile, test_multi_file, compression='rice') + assert_raises(OSError, galsim.fits.readMulti, hdu_list=pyfits.HDUList()) + assert_raises(OSError, galsim.fits.readMulti, hdu_list=pyfits.HDUList(), compression='rice') assert_raises(TypeError, galsim.fits.readMulti) assert_raises(TypeError, galsim.fits.readMulti, test_multi_file, hdu_list=hdu) @@ -733,19 +769,25 @@ def test_Image_MultiFITS_IO(): assert_raises(TypeError, galsim.fits.writeMulti, image_list, file_name=test_multi_file, hdu_list=hdu) + assert_raises(OSError, galsim.fits.writeMulti, image_list, test_multi_file, clobber=False) + assert_raises(TypeError, galsim.fits.writeFile) assert_raises(TypeError, galsim.fits.writeFile, image_list) - assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + assert_raises(ValueError, galsim.fits.writeFile, test_multi_file, image_list, compression='invalid') - assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + assert_raises(ValueError, galsim.fits.writeFile, test_multi_file, image_list, compression='rice') - assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + assert_raises(ValueError, galsim.fits.writeFile, test_multi_file, image_list, compression='gzip_tile') - assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + assert_raises(ValueError, galsim.fits.writeFile, test_multi_file, image_list, compression='hcompress') - assert_raises(ValueError, galsim.fits.writeFile, image_list, test_multi_file, + assert_raises(ValueError, galsim.fits.writeFile, test_multi_file, image_list, compression='plio') + galsim.fits.writeFile(test_multi_file, hdu_list) + assert_raises(OSError, galsim.fits.writeFile, test_multi_file, image_list, clobber=False) + + # # Test various compression schemes # @@ -756,6 +798,8 @@ def test_Image_MultiFITS_IO(): if i > 0 and __name__ != "__main__": continue + test_multi_file0 = test_multi_file + # Test full-file gzip test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+".fits.gz") test_image_list = galsim.fits.readMulti(test_multi_file, compression='gzip') @@ -785,6 +829,15 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for auto full-file gzip") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image_list = galsim.fits.readMulti(test_multi_file, compression=None) + for k in range(nimages): + np.testing.assert_array_equal((ref_array+k).astype(types[i]), + test_image_list[k].array, + err_msg="Image"+tchar[i]+" writeMulti failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='gzip') + # Test full-file bzip2 test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+".fits.bz2") test_image_list = galsim.fits.readMulti(test_multi_file, compression='bzip2') @@ -814,6 +867,15 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for auto full-file bzip2") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image_list = galsim.fits.readMulti(test_multi_file, compression=None) + for k in range(nimages): + np.testing.assert_array_equal((ref_array+k).astype(types[i]), + test_image_list[k].array, + err_msg="Image"+tchar[i]+" writeMulti failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='bzip2') + # Test rice test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+".fits.fz") test_image_list = galsim.fits.readMulti(test_multi_file, compression='rice') @@ -843,6 +905,9 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for auto rice") + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='rice') + assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='none') + # Test gzip_tile test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+"_internal.fits.gzt") galsim.fits.writeMulti(image_list,test_multi_file, compression='gzip_tile') @@ -852,6 +917,9 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for gzip_tile") + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='gzip_tile') + assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='none') + # Test hcompress test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+"_internal.fits.hc") galsim.fits.writeMulti(image_list,test_multi_file, compression='hcompress') @@ -861,6 +929,9 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for hcompress") + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='hcompress') + assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='none') + # Test plio (only valid on positive integer values) if tchar[i] in ['S', 'I']: test_multi_file = os.path.join(datadir, "test_multi"+tchar[i]+"_internal.fits.plio") @@ -871,6 +942,9 @@ def test_Image_MultiFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeMulti failed for plio") + assert_raises(OSError, galsim.fits.readMulti, test_multi_file0, compression='plio') + assert_raises(OSError, galsim.fits.readMulti, test_multi_file, compression='none') + @timer def test_Image_CubeFITS_IO(): @@ -1028,6 +1102,8 @@ def test_Image_CubeFITS_IO(): assert_raises(TypeError, galsim.fits.writeCube, image_list, file_name=test_cube_file, hdu_list=hdu_list) + assert_raises(OSError, galsim.fits.writeCube, image_list, test_cube_file, clobber=False) + assert_raises(ValueError, galsim.fits.writeCube, image_list[:0], test_cube_file) assert_raises(ValueError, galsim.fits.writeCube, [image_list[0], image_list[1].subImage(galsim.BoundsI(0,4,0,4))], @@ -1043,6 +1119,8 @@ def test_Image_CubeFITS_IO(): if i > 0 and __name__ != "__main__": continue + test_cube_file0 = test_cube_file + # Test full-file gzip test_cube_file = os.path.join(datadir, "test_cube"+tchar[i]+".fits.gz") test_image_list = galsim.fits.readCube(test_cube_file, compression='gzip') @@ -1072,6 +1150,15 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeCube failed for auto full-file gzip") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image_list = galsim.fits.readCube(test_cube_file, compression=None) + for k in range(nimages): + np.testing.assert_array_equal((ref_array+k).astype(types[i]), + test_image_list[k].array, + err_msg="Image"+tchar[i]+" writeCube failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='gzip') + # Test full-file bzip2 test_cube_file = os.path.join(datadir, "test_cube"+tchar[i]+".fits.bz2") test_image_list = galsim.fits.readCube(test_cube_file, compression='bzip2') @@ -1101,6 +1188,15 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeCube failed for auto full-file bzip2") + # With compression = None or 'none', astropy automatically figures it out anyway. + test_image_list = galsim.fits.readCube(test_cube_file, compression=None) + for k in range(nimages): + np.testing.assert_array_equal((ref_array+k).astype(types[i]), + test_image_list[k].array, + err_msg="Image"+tchar[i]+" writeCube failed for auto full-file gzip") + + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='bzip2') + # Test rice test_cube_file = os.path.join(datadir, "test_cube"+tchar[i]+".fits.fz") test_image_list = galsim.fits.readCube(test_cube_file, compression='rice') @@ -1130,6 +1226,10 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeCube failed for auto rice") + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='rice') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='none') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, hdu=1, compression='none') + # Test gzip_tile test_cube_file = os.path.join(datadir, "test_cube"+tchar[i]+"_internal.fits.gzt") galsim.fits.writeCube(image_list,test_cube_file, compression='gzip_tile') @@ -1139,7 +1239,20 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeCube failed for gzip_tile") - # Note: hcompress is invalid for data cubes + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='gzip_tile') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='none') + + # Test hcompress + test_cube_file = os.path.join(datadir, "test_cube"+tchar[i]+"_internal.fits.hc") + galsim.fits.writeCube(image_list,test_cube_file, compression='hcompress') + test_image_list = galsim.fits.readCube(test_cube_file, compression='hcompress') + for k in range(nimages): + np.testing.assert_array_equal((ref_array+k).astype(types[i]), + test_image_list[k].array, + err_msg="Image"+tchar[i]+" writeCube failed for hcompress") + + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='hcompress') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='none') # Test plio (only valid on positive integer values) if tchar[i] in ['S', 'I']: @@ -1151,6 +1264,9 @@ def test_Image_CubeFITS_IO(): test_image_list[k].array, err_msg="Image"+tchar[i]+" writeCube failed for plio") + assert_raises(OSError, galsim.fits.readCube, test_cube_file0, compression='plio') + assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='none') + @timer def test_Image_array_view(): @@ -2264,6 +2380,9 @@ def test_Image_writeheader(): assert key_name.upper() in new_header.keys() assert new_header['CD1_1'] == 0.0 + # If clobbert = False, then trying to overwrite will raise an OSError + assert_raises(OSError, im_test.write, test_file, clobber=False) + @timer def test_ne(): diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index 62720017e0b..fad67c5fefa 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -163,6 +163,7 @@ def test_roundtrip(): assert_raises(ValueError, galsim.Interpolant.from_name, 'lanczos3A') assert_raises(ValueError, galsim.Interpolant.from_name, 'lanczosF') assert_raises(ValueError, galsim.Interpolant.from_name, 'lanzos') + assert_raises(NotImplementedError, galsim.Interpolant) @timer def test_fluxnorm(): @@ -271,6 +272,7 @@ def test_exceptions(): assert_raises(ValueError, galsim.InterpolatedImage, im, pad_factor=-1.) assert_raises(ValueError, galsim.InterpolatedImage, im, noise_pad_size=33, noise_pad=im.wcs) assert_raises(ValueError, galsim.InterpolatedImage, im, noise_pad_size=33, noise_pad=-1.) + assert_raises(ValueError, galsim.InterpolatedImage, im, noise_pad_size=-33, noise_pad=1.) @timer diff --git a/tests/test_phase_psf.py b/tests/test_phase_psf.py index 7ef5dc55dda..1943dfb28c3 100644 --- a/tests/test_phase_psf.py +++ b/tests/test_phase_psf.py @@ -37,7 +37,7 @@ def test_aperture(): aper1 = galsim.Aperture(diam=1.7) im = galsim.fits.read(os.path.join(imgdir, pp_file)) aper2 = galsim.Aperture(diam=1.7, pupil_plane_im=im) - aper3 = galsim.Aperture(diam=1.7, nstruts=4) + aper3 = galsim.Aperture(diam=1.7, nstruts=4, gsparams=galsim.GSParams(maximum_fft_size=4096)) do_pickle(aper1) do_pickle(aper2) do_pickle(aper3) diff --git a/tests/test_random.py b/tests/test_random.py index 38599497fa1..84e68aff7e1 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -243,6 +243,9 @@ def test_uniform(): assert_raises(TypeError, galsim.UniformDeviate, list()) assert_raises(TypeError, galsim.UniformDeviate, set()) + assert_raises(TypeError, u.seed, '123') + assert_raises(TypeError, u.seed, 12.3) + @timer def test_gaussian(): @@ -389,6 +392,8 @@ def test_gaussian(): assert_raises(TypeError, galsim.GaussianDeviate, list()) assert_raises(TypeError, galsim.GaussianDeviate, set()) + assert_raises(ValueError, galsim.GaussianDeviate, testseed, mean=1, sigma=-1) + @timer def test_binomial(): diff --git a/tests/test_real.py b/tests/test_real.py index eb5486b8d04..a321d8d46cf 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -56,14 +56,18 @@ def test_real_galaxy_catalog(): # Start with the test RGC that we will use throughout this test file. rgc = galsim.RealGalaxyCatalog(file_name=catalog_file, dir=image_dir) + assert len(rgc) == rgc.nobjects == rgc.getNObjects() == 2 + assert rgc.file_name == os.path.join(image_dir, catalog_file) + assert rgc.image_dir == image_dir + print('sample = ',rgc.sample) print('ident = ',rgc.ident) - assert(rgc.sample == None) - assert(len(rgc.ident) == 2) + assert rgc.sample == None + assert len(rgc.ident) == 2 gal1 = rgc.getGalImage(0) assert isinstance(gal1, galsim.Image) - psf1 = rgc.getGalImage(0) + psf1 = rgc.getPSFImage(0) assert isinstance(psf1, galsim.Image) noise, scale, var = rgc.getNoiseProperties(0) assert noise is None # No noise images for the test catalog. @@ -73,6 +77,19 @@ def test_real_galaxy_catalog(): assert rgc.getIndexForID(100533) == 0 + # With _nobjects_only=True, it doesn't finish loadin + rgc2 = galsim.RealGalaxyCatalog(file_name=catalog_file, dir=image_dir, _nobjects_only=True) + assert len(rgc2) == rgc2.nobjects == rgc2.getNObjects() == 2 + assert rgc2.file_name == os.path.join(image_dir, catalog_file) + assert rgc2.image_dir == image_dir + assert rgc2.sample == None + with assert_raises(AttributeError): + rgc2.ident + with assert_raises(AttributeError): + rgc2.getGalImage(0) + with assert_raises(AttributeError): + rgc2.getPSFImage(0) + assert_raises(TypeError, galsim.RealGalaxyCatalog, catalog_file, dir=image_dir, sample='25.2') assert_raises(ValueError, galsim.RealGalaxyCatalog, sample='23.2') assert_raises(ValueError, galsim.RealGalaxyCatalog, sample='23.2') @@ -87,6 +104,10 @@ def test_real_galaxy_catalog(): rgc.noise_file_name = [ 'none' for i in rgc.ident ] assert_raises(IndexError, rgc.getNoiseProperties, 5) + assert_raises(OSError, galsim.RealGalaxyCatalog, dir=image_dir) + assert_raises(OSError, galsim.RealGalaxyCatalog, file_name='25.2.fits', dir=image_dir) + assert_raises(OSError, galsim.RealGalaxyCatalog, file_name='23.5.fits', dir='invalid') + # Now test out the real ones. But if they aren't installed, abort gracefully. try: rgc = galsim.RealGalaxyCatalog(sample='25.2') diff --git a/tests/test_sed.py b/tests/test_sed.py index b6512094f37..77cc2d588ab 100644 --- a/tests/test_sed.py +++ b/tests/test_sed.py @@ -531,11 +531,11 @@ def test_SED_withFlux(): "Calculating SED flux from sed * bp failed.") # Invalid for dimensionless SED - c = galsim.SED(2.0, 'nm', '1') + flat = galsim.SED(2.0, 'nm', '1') with assert_raises(galsim.GalSimSEDError): - c.withFlux(1.0, rband) + flat.withFlux(1.0, rband) with assert_raises(galsim.GalSimSEDError): - c.calculateFlux(rband) + flat.calculateFlux(rband) @timer @@ -558,9 +558,9 @@ def test_SED_withFluxDensity(): a(500), 3.0, 5, "Setting SED flux density failed.") # Invalid for dimensionless SED - c = galsim.SED(2.0, 'nm', '1') + flat = galsim.SED(2.0, 'nm', '1') with assert_raises(galsim.GalSimSEDError): - c.withFluxDensity(1.0, 500) + flat.withFluxDensity(1.0, 500) @timer @@ -624,9 +624,9 @@ def test_SED_calculateMagnitude(): assert (abs((AB_mag - vega_mag) - conversion) < thresh) # Invalid for dimensionless SED - c = galsim.SED(2.0, 'nm', '1') + flat = galsim.SED(2.0, 'nm', '1') with assert_raises(galsim.GalSimSEDError): - c.withMagnitude(24.0, bandpass) + flat.withMagnitude(24.0, bandpass) # Zeropoint needs to be set. bp = galsim.Bandpass(galsim.LookupTable([1,2,3,4,5], [1,2,3,4,5]), 'nm') @@ -749,6 +749,10 @@ def test_SED_calculateSeeingMomentRatio(): den = np.trapz(sed(waves), waves) np.testing.assert_almost_equal(relative_size, num/den, 4) + # Invalid for dimensionless SED + flat = galsim.SED(2.0, 'nm', '1') + with assert_raises(galsim.GalSimSEDError): + flat.calculateSeeingMomentRatio(bandpass) @timer def test_SED_sampleWavelength(): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 2227427e399..0819489f44b 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -889,6 +889,8 @@ def test_ne(): with assert_raises(galsim.GalSimError): sbp = degen._sbp + assert_raises(TypeError, galsim.Transform, jac) + @timer def test_compound(): diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 6a84dbb9619..269b70bb803 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -71,16 +71,20 @@ def test_pos(): assert isinstance(pd5.x, float) assert isinstance(pd5.y, float) + assert_raises(TypeError, galsim.PositionI, 11) assert_raises(TypeError, galsim.PositionI, 11, 23, 9) assert_raises(TypeError, galsim.PositionI, x=11, z=23) assert_raises(TypeError, galsim.PositionI, x=11) - assert_raises(TypeError, galsim.PositionI, 11) + assert_raises(TypeError, galsim.PositionD, x=11, y=23, z=17) + assert_raises(TypeError, galsim.PositionI, 11, 23, x=13, z=21) assert_raises(TypeError, galsim.PositionI, 11, 23.5) + assert_raises(TypeError, galsim.PositionD, 11) assert_raises(TypeError, galsim.PositionD, 11, 23, 9) assert_raises(TypeError, galsim.PositionD, x=11, z=23) assert_raises(TypeError, galsim.PositionD, x=11) - assert_raises(TypeError, galsim.PositionD, 11) + assert_raises(TypeError, galsim.PositionD, x=11, y=23, z=17) + assert_raises(TypeError, galsim.PositionD, 11, 23, x=13, z=21) assert_raises(ValueError, galsim.PositionD, 11, "blue") # Can't use base class directly. @@ -129,9 +133,14 @@ def test_pos(): assert pd9 == 0*pd1 assert isinstance(pd9, galsim.PositionD) + assert_raises(TypeError, pd1.__add__, 11) + assert_raises(TypeError, pd1.__sub__, 11) assert_raises(TypeError, pd1.__mul__, "11") assert_raises(TypeError, pd1.__mul__, None) assert_raises(TypeError, pd1.__div__, "11e") + + assert_raises(TypeError, pi1.__add__, 11) + assert_raises(TypeError, pi1.__sub__, 11) assert_raises(TypeError, pi1.__mul__, "11e") assert_raises(TypeError, pi1.__mul__, None) assert_raises(TypeError, pi1.__div__, 11.5) @@ -165,7 +174,10 @@ def test_bounds(): bi11 = galsim.BoundsI(galsim.BoundsD(11.,23.,17.,50.)) bi12 = galsim.BoundsI(xmin=11,ymin=17,xmax=23,ymax=50) bi13 = galsim._BoundsI(11,23,17,50) - for b in [bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11, bi12, bi13]: + bi14 = galsim.BoundsI() + bi14 += galsim.PositionI(11,17) + bi14 += galsim.PositionI(23,50) + for b in [bi1, bi2, bi3, bi4, bi5, bi6, bi7, bi8, bi9, bi10, bi11, bi12, bi13, bi14]: assert b.isDefined() assert b == bi1 assert isinstance(b.xmin, int) @@ -198,7 +210,10 @@ def test_bounds(): bd11 = galsim.BoundsD(galsim.BoundsI(11,23,17,50)) bd12 = galsim.BoundsD(xmin=11.0,ymin=17.0,xmax=23.0,ymax=50.0) bd13 = galsim._BoundsD(11,23,17,50) - for b in [bd1, bd2, bd3, bd4, bd5, bd6, bd7, bd8, bd9, bd10, bd11, bd12, bd13]: + bd14 = galsim.BoundsD() + bd14 += galsim.PositionD(11.,17.) + bd14 += galsim.PositionD(23,50) + for b in [bd1, bd2, bd3, bd4, bd5, bd6, bd7, bd8, bd9, bd10, bd11, bd12, bd13, bd14]: assert b.isDefined() assert b == bd1 assert isinstance(b.xmin, float) @@ -209,19 +224,27 @@ def test_bounds(): assert b.center == galsim.PositionD(17, 33.5) assert b.true_center == galsim.PositionD(17, 33.5) + assert_raises(TypeError, galsim.BoundsI, 11) + assert_raises(TypeError, galsim.BoundsI, 11, 23) assert_raises(TypeError, galsim.BoundsI, 11, 23, 9) assert_raises(TypeError, galsim.BoundsI, 11, 23, 9, 12, 59) assert_raises(TypeError, galsim.BoundsI, xmin=11, xmax=23, ymin=17, ymax=50, z=23) assert_raises(TypeError, galsim.BoundsI, xmin=11, xmax=50) - assert_raises(TypeError, galsim.BoundsI, 11) assert_raises(TypeError, galsim.BoundsI, 11, 23.5, 17, 50.9) + assert_raises(TypeError, galsim.BoundsI, 11, 23, 9, 12, xmin=19, xmax=2) + with assert_raises(TypeError): + bi1 += (11,23) + assert_raises(TypeError, galsim.BoundsD, 11) + assert_raises(TypeError, galsim.BoundsD, 11, 23) assert_raises(TypeError, galsim.BoundsD, 11, 23, 9) assert_raises(TypeError, galsim.BoundsD, 11, 23, 9, 12, 59) assert_raises(TypeError, galsim.BoundsD, xmin=11, xmax=23, ymin=17, ymax=50, z=23) assert_raises(TypeError, galsim.BoundsD, xmin=11, xmax=50) - assert_raises(TypeError, galsim.BoundsD, 11) assert_raises(ValueError, galsim.BoundsD, 11, 23, 17, "blue") + assert_raises(TypeError, galsim.BoundsD, 11, 23, 9, 12, xmin=19, xmax=2) + with assert_raises(TypeError): + bd1 += (11,23) # Can't use base class directly. assert_raises(TypeError, galsim.Bounds, 11, 23, 9, 12) @@ -232,11 +255,24 @@ def test_bounds(): assert bi1 == bi1 & galsim.BoundsI(0,100,0,100) assert bi1 == galsim.BoundsI(0,23,0,50) & galsim.BoundsI(11,100,17,100) assert bi1 == galsim.BoundsI(0,23,17,100) & galsim.BoundsI(11,100,0,50) + assert not (bi1 & galsim.BoundsI()).isDefined() + assert not (galsim.BoundsI() & bi1).isDefined() assert bd1 == galsim.BoundsD(0,100,0,100) & bd1 assert bd1 == bd1 & galsim.BoundsD(0,100,0,100) assert bd1 == galsim.BoundsD(0,23,0,50) & galsim.BoundsD(11,100,17,100) assert bd1 == galsim.BoundsD(0,23,17,100) & galsim.BoundsD(11,100,0,50) + assert not (bd1 & galsim.BoundsD()).isDefined() + assert not (galsim.BoundsD() & bd1).isDefined() + + with assert_raises(TypeError): + bi1 & galsim.PositionI(1,2) + with assert_raises(TypeError): + bi1 & galsim.PositionD(1,2) + with assert_raises(TypeError): + bd1 & galsim.PositionI(1,2) + with assert_raises(TypeError): + bd1 & galsim.PositionD(1,2) # Check withBorder assert bi1.withBorder(4) == galsim.BoundsI(7,27,13,54) @@ -721,6 +757,7 @@ def test_interleaveImages(): assert_raises(TypeError, interleaveImages, im_list=img, N=n, offsets=offset_list) assert_raises(ValueError, interleaveImages, [img], N=1, offsets=offset_list) + assert_raises(ValueError, interleaveImages, im_list, n, offset_list[:-1]) assert_raises(TypeError, interleaveImages, [im.array for im in im_list], n, offset_list) assert_raises(TypeError, interleaveImages, [im_list[0]] + [im.array for im in im_list[1:]], diff --git a/tests/test_wcs.py b/tests/test_wcs.py index 122c491ebd0..508070aa36c 100644 --- a/tests/test_wcs.py +++ b/tests/test_wcs.py @@ -1852,6 +1852,11 @@ def test_pyastwcs(): approx = tag in [ 'SIP' ] do_ref(wcs, ref_list, 'PyAstWCS '+tag, approx) + if tag == 'TAN': + # Also check origin. (Now that reference checks are done.) + wcs = galsim.PyAstWCS(file_name, dir=dir, compression='none', hdu=0, + origin=galsim.PositionD(3,4)) + do_celestial_wcs(wcs, 'PyAst file '+file_name) # TAN-FLIP has an error of 4mas after write and read here, which I don't really understand. @@ -1877,6 +1882,10 @@ def test_pyastwcs(): with assert_raises(galsim.GalSimError): galsim.PyAstWCS('SBProfile_comparison_images/kolmogorov.fits') + # This file does not have any WCS information in it. + with assert_raises(OSError): + galsim.PyAstWCS('fits_files/blankimg.fits') + assert_raises(TypeError, galsim.PyAstWCS) assert_raises(TypeError, galsim.PyAstWCS, file_name, header='dummy') assert_raises(TypeError, galsim.PyAstWCS, file_name, wcsinfo=wcsinfo) @@ -1926,9 +1935,13 @@ def test_wcstools(): do_wcs_image(wcs, 'WcsToolsWCS_'+tag) - # TAN-PV is one of the ones that WcsToolsWCS doesn't support. - #with assert_raises(galsim.GalSimValueError): - galsim.WcsToolsWCS(references['TAN-PV'][0], dir=dir) + # HPX is one of the ones that WcsToolsWCS doesn't support. + with assert_raises(galsim.GalSimError): + galsim.WcsToolsWCS(references['HPX'][0], dir=dir) + + # This file does not have any WCS information in it. + with assert_raises(OSError): + galsim.WcsToolsWCS('fits_files/blankimg.fits') # Doesn't support LINEAR WCS types. with assert_raises(galsim.GalSimError): @@ -1961,6 +1974,11 @@ def test_gsfitswcs(): do_ref(wcs, ref_list, 'GSFitsWCS '+tag) + if tag == 'TAN': + # Also check origin. (Now that reference checks are done.) + wcs = galsim.GSFitsWCS(file_name, dir=dir, compression='none', hdu=0, + origin=galsim.PositionD(3,4)) + do_celestial_wcs(wcs, 'GSFitsWCS '+file_name) do_wcs_image(wcs, 'GSFitsWCS_'+tag) @@ -1973,6 +1991,10 @@ def test_gsfitswcs(): with assert_raises(galsim.GalSimError): galsim.GSFitsWCS('SBProfile_comparison_images/kolmogorov.fits') + # This file does not have any WCS information in it. + with assert_raises(galsim.GalSimError): + galsim.GSFitsWCS('fits_files/blankimg.fits') + assert_raises(TypeError, galsim.GSFitsWCS) assert_raises(TypeError, galsim.GSFitsWCS, file_name, header='dummy') @@ -2082,6 +2104,10 @@ def test_fitswcs(): linear = galsim.FitsWCS('SBProfile_comparison_images/kolmogorov.fits') assert isinstance(linear, galsim.OffsetWCS) + # This file does not have any WCS information in it. + pixel = galsim.FitsWCS('fits_files/blankimg.fits') + assert pixel == galsim.PixelScale(1.0) + assert_raises(TypeError, galsim.FitsWCS) assert_raises(TypeError, galsim.FitsWCS, file_name, header='dummy') diff --git a/tests/test_wfirst.py b/tests/test_wfirst.py index d96e3266b6b..4d94dc82783 100644 --- a/tests/test_wfirst.py +++ b/tests/test_wfirst.py @@ -499,6 +499,7 @@ def test_wfirst_detectors(): np.testing.assert_array_equal( im_2.array, im_1.array, err_msg='Persistence results depend on function used.') + assert_raises(TypeError, galsim.wfirst.applyPersistence, im_2, im0) # Then we do IPC: im_1 = im.copy() @@ -540,7 +541,7 @@ def test_wfirst_psfs(): # - achromatic PSFs without loading the pupil plane image. # First test: check that if we don't specify SCAs, then we get all the expected ones. - wfirst_psfs = galsim.wfirst.getPSF(approximate_struts=True) + wfirst_psfs = galsim.wfirst.getPSF() got_scas = np.array(list(wfirst_psfs.keys())) expected_scas = np.arange(1, galsim.wfirst.n_sca+1, 1) np.testing.assert_array_equal( From e43d447a4878bddf2caf97aeaa15c0bdf69d4630 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 4 May 2018 00:28:51 -0400 Subject: [PATCH 67/96] Allow non-standard samples. Addresses some of desiderata in #795 (#755) --- galsim/real.py | 18 ++++++++++-------- tests/test_real.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/galsim/real.py b/galsim/real.py index efe617257a9..22b25e0db86 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -847,21 +847,23 @@ def _parse_files_dirs(file_name, image_dir, sample): use_sample = None else: use_sample = sample - if use_sample not in ('23.5', '25.2'): - raise GalSimValueError("Sample name not recognized.",use_sample, ('23.5', '25.2')) - # after that piece of code, use_sample is either "23.5", "25.2" (if using one of the default - # catalogs) or it is still None, if a file_name was given. if file_name is None: file_name = 'real_galaxy_catalog_' + use_sample + '.fits' if image_dir is None: + use_meta_dir = True # Used to give a more helpful error message image_dir = os.path.join(meta_data.share_dir, 'COSMOS_'+use_sample+'_training_sample') + else: + use_meta_dir = False full_file_name = os.path.join(image_dir,file_name) - if not os.path.isfile(full_file_name): - raise OSError('No RealGalaxy catalog found in %s. Run the program ' - 'galsim_download_cosmos -s %s to download catalog and accompanying ' - 'image files.'%(image_dir, use_sample)) + if not os.path.isfile(full_file_name) and use_meta_dir: + if use_sample not in ('23.5', '25.2'): + raise GalSimValueError("Sample name not recognized.",use_sample, ('23.5', '25.2')) + else: + raise OSError('No RealGalaxy catalog found in %s. Run the program ' + 'galsim_download_cosmos -s %s to download catalog and accompanying ' + 'image files.'%(image_dir, use_sample)) elif image_dir is None: full_file_name = file_name image_dir = os.path.dirname(file_name) diff --git a/tests/test_real.py b/tests/test_real.py index a321d8d46cf..8f99061a893 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -108,6 +108,11 @@ def test_real_galaxy_catalog(): assert_raises(OSError, galsim.RealGalaxyCatalog, file_name='25.2.fits', dir=image_dir) assert_raises(OSError, galsim.RealGalaxyCatalog, file_name='23.5.fits', dir='invalid') + # Test the catalog used by a few demos. + rgc = galsim.RealGalaxyCatalog(sample='23.5_example', dir='../examples/data') + assert(rgc.sample == '23.5_example') + assert(len(rgc.ident) == 100) + # Now test out the real ones. But if they aren't installed, abort gracefully. try: rgc = galsim.RealGalaxyCatalog(sample='25.2') @@ -129,6 +134,18 @@ def test_real_galaxy_catalog(): assert(rgc.sample == '23.5') assert(len(rgc.ident) == 56062) + # Check error message if COSMOS galaxies aren't in share_dir. Do this by temporarily + # changing share_dir value. + save = galsim.meta_data.share_dir + galsim.meta_data.share_dir = image_dir + try: + rgc = galsim.RealGalaxyCatalog(sample='23.5') + except OSError as err: + assert 'Run the program galsim_download_cosmos -s 23.5' in str(err) + else: + assert False, "Automatic sample=23.5 should have failed with share_dir = " + image_dir + galsim.meta_data.share_dir = save + @timer def test_real_galaxy_ideal(): From 659483b0c3141d040df79dce14169e21defdb08a Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 4 May 2018 00:29:50 -0400 Subject: [PATCH 68/96] A few more test coverage bits (#755) --- galsim/fits.py | 4 ++-- galsim/image.py | 4 ---- galsim/phase_psf.py | 2 +- galsim/utilities.py | 6 +++--- tests/test_image.py | 4 ++++ tests/test_wcs.py | 4 ++++ 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/galsim/fits.py b/galsim/fits.py index 3afc21704ca..4a04899d70b 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -84,7 +84,7 @@ def gunzip_call(self, file): # Convert to a NotImplementedError, so we can try a different method. raise NotImplementedError() ret = p.communicate() - if ret == '': + if ret == '': # pragma: no cover raise OSError("Error running gunzip. stderr output = %s"%ret[1]) if p.returncode != 0: # pragma: no cover raise OSError("Error running gunzip. Return code = %s"%p.returncode) @@ -121,7 +121,7 @@ def bunzip2_call(self, file): # Convert to a NotImplementedError, so we can try a different method. raise NotImplementedError() ret = p.communicate() - if ret == '': + if ret == '': # pragma: no cover raise OSError("Error running bunzip2. stderr output = %s"%ret[1]) if p.returncode != 0: # pragma: no cover raise OSError("Error running bunzip2. Return code = %s"%p.returncode) diff --git a/galsim/image.py b/galsim/image.py index 9fee99d2154..82e45a99ac5 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -387,10 +387,6 @@ def __init__(self, *args, **kwargs): raise TypeError("wcs parameters must be a galsim.BaseWCS instance") self.wcs = wcs - # dtype is read-only - @property - def dtype(self): return self._dtype - @staticmethod def _get_xmin_ymin(array, kwargs): """A helper function for parsing xmin, ymin, bounds options with a given array diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 7dbfa49f5a6..d8c7e124521 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -510,7 +510,7 @@ def __repr__(self): tmp = self.illuminated.astype(np.int16).tolist() s += ", pupil_plane_im=array(%r"%tmp+", dtype='int16')" s += ", pupil_plane_scale=%r"%self.pupil_plane_scale - if hasattr(self, '_gsparams') and self._gsparams is not None: + if self._gsparams != GSParams(): s += ", gsparams=%r"%self._gsparams s += ")" return s diff --git a/galsim/utilities.py b/galsim/utilities.py index a8ef79cdec5..1b2fbb80cf1 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -1464,7 +1464,7 @@ def __init__(self, f): self.f = f.__func__ self.c = weakref.ref(f.__self__) def __call__(self, *args): - if self.c() is None : + if self.c() is None : # pragma: no cover raise TypeError('Method called on dead object') return self.f(self.c(), *args) @@ -1484,14 +1484,14 @@ def EnsureDir(target): if not exists: try: os.makedirs(dir) - except OSError as err: + except OSError as err: # pragma: no cover # check if the file now exists, which can happen if some other # process created the directory between the os.path.exists call # above and the time of the makedirs attempt. This is OK if err.errno != _ERR_FILE_EXISTS: raise err - elif exists and not os.path.isdir(dir): + elif exists and not os.path.isdir(dir): # pragma: no cover raise OSError("tried to make directory '%s' " "but a non-directory file of that " "name already exists" % dir) diff --git a/tests/test_image.py b/tests/test_image.py index da5440c95dc..1b22622f290 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -503,6 +503,7 @@ def test_Image_FITS_IO(): assert_raises(ValueError, galsim.fits.read, test_file, compression='invalid') assert_raises(ValueError, ref_image.write, test_file, compression='invalid') assert_raises(OSError, galsim.fits.read, test_file, compression='rice') + assert_raises(OSError, galsim.fits.read, 'invalid.fits') assert_raises(TypeError, galsim.fits.read) assert_raises(TypeError, galsim.fits.read, test_file, hdu_list=hdu) @@ -760,6 +761,8 @@ def test_Image_MultiFITS_IO(): assert_raises(OSError, galsim.fits.readFile, test_multi_file, compression='rice') assert_raises(OSError, galsim.fits.readMulti, hdu_list=pyfits.HDUList()) assert_raises(OSError, galsim.fits.readMulti, hdu_list=pyfits.HDUList(), compression='rice') + assert_raises(OSError, galsim.fits.readMulti, 'invalid.fits') + assert_raises(OSError, galsim.fits.readFile, 'invalid.fits') assert_raises(TypeError, galsim.fits.readMulti) assert_raises(TypeError, galsim.fits.readMulti, test_multi_file, hdu_list=hdu) @@ -1093,6 +1096,7 @@ def test_Image_CubeFITS_IO(): assert_raises(ValueError, galsim.fits.writeFile, image_list, test_cube_file, compression='invalid') assert_raises(OSError, galsim.fits.readCube, test_cube_file, compression='rice') + assert_raises(OSError, galsim.fits.readCube, 'invalid.fits') assert_raises(TypeError, galsim.fits.readCube) assert_raises(TypeError, galsim.fits.readCube, test_cube_file, hdu_list=hdu) diff --git a/tests/test_wcs.py b/tests/test_wcs.py index 508070aa36c..4b70d89d0d4 100644 --- a/tests/test_wcs.py +++ b/tests/test_wcs.py @@ -1797,6 +1797,10 @@ def test_astropywcs(): wcs2 = galsim.AstropyWCS(header=wcs.header) do_celestial_wcs(wcs2, 'AstropyWCS from header', test_pickle=True) + # Astropy gives an error when trying to read this one. + with assert_raises(OSError): + wcs = galsim.AstropyWCS(references['TAN-PV'][0], dir=dir) + # Doesn't support LINEAR WCS types. with assert_raises(galsim.GalSimError): galsim.AstropyWCS('SBProfile_comparison_images/kolmogorov.fits') From d24118435b13dfeb07fb739094961e00cbef1ed3 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 4 May 2018 02:09:21 -0400 Subject: [PATCH 69/96] Make OpticalPSF _psf a lazy_property, so wfirst psf tests don't take forever (#755) --- galsim/phase_psf.py | 27 +++++++++------------------ tests/test_wfirst.py | 15 ++++++++++----- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index d8c7e124521..ee7f2d7873a 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -1705,16 +1705,17 @@ def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., def self._force_maxk = _force_maxk self._ii_pad_factor = ii_pad_factor - # Finally, put together to make the PSF. - self._psf = PhaseScreenPSF(self._screens, lam=self._lam, flux=self._flux, - aper=aper, interpolant=self._interpolant, + @lazy_property + def _psf(self): + psf = PhaseScreenPSF(self._screens, lam=self._lam, flux=self._flux, + aper=self._aper, interpolant=self._interpolant, scale_unit=self._scale_unit, gsparams=self._gsparams, suppress_warning=self._suppress_warning, geometric_shooting=self._geometric_shooting, - _force_stepk=_force_stepk, _force_maxk=_force_maxk, - ii_pad_factor=ii_pad_factor) - - self._psf._prepareDraw() # No need to delay an OpticalPSF. + _force_stepk=self._force_stepk, _force_maxk=self._force_maxk, + ii_pad_factor=self._ii_pad_factor) + psf._prepareDraw() # No need to delay an OpticalPSF. + return psf def __str__(self): screen = self._psf._screen_list[0] @@ -1777,21 +1778,11 @@ def __getstate__(self): # The SBProfile is picklable, but it is pretty inefficient, due to the large images being # written as a string. Better to pickle the psf and remake the PhaseScreenPSF. d = self.__dict__.copy() - d['aper'] = d['_psf'].aper - del d['_psf'] + d.pop('_psf', None) return d def __setstate__(self, d): self.__dict__ = d - aper = self.__dict__.pop('aper') - self._psf = PhaseScreenPSF(self._screens, lam=self._lam, flux=self._flux, - aper=aper, interpolant=self._interpolant, - scale_unit=self._scale_unit, gsparams=self._gsparams, - suppress_warning=self._suppress_warning, - _force_stepk=self._force_stepk, - _force_maxk=self._force_maxk, - ii_pad_factor=self._ii_pad_factor) - self._psf._prepareDraw() @property def _maxk(self): diff --git a/tests/test_wfirst.py b/tests/test_wfirst.py index 4d94dc82783..56b442adbe2 100644 --- a/tests/test_wfirst.py +++ b/tests/test_wfirst.py @@ -550,14 +550,19 @@ def test_wfirst_psfs(): # Check that if we specify SCAs, then we get the ones we specified. expected_scas = [5, 7, 14] - wfirst_psfs = galsim.wfirst.getPSF(SCAs=expected_scas, - approximate_struts=True) + wfirst_psfs = galsim.wfirst.getPSF(SCAs=expected_scas) got_scas = list(wfirst_psfs.keys()) # Have to sort it in numerical order for this comparison. got_scas.sort() got_scas = np.array(got_scas) np.testing.assert_array_equal( got_scas, expected_scas, err_msg='List of SCAs was not as requested') + for sca in got_scas: + assert isinstance(wfirst_psfs[sca], galsim.ChromaticObject) + + # Providing a wavelength returns achromatic PSFs + psfs_5 = galsim.wfirst.getPSF(SCAs=5, wavelength=1950.) + assert isinstance(psfs_5[5], galsim.GSObject) # Check that if we specify a particular wavelength, the PSF that is drawn is the same as if we # had gotten chromatic PSFs and then used evaluateAtWavelength. Note that this nominally seems @@ -616,9 +621,9 @@ def test_wfirst_psfs(): 'wavelength disagree.') # Check some invalid inputs. - # Note, this is a total cheat for getting test coverage of the high_accuracy and - # non-approximate_struts branches in getPSF. The actual test of this functionality comes - # below, but it is only run for __name__==__main__ runs (i.e. run_all_tests). + # Note, this is a total cheat for getting test coverage of the high_accuracy branches + # in getPSF. The actual test of this functionality comes below, but it is only run for + # __name__==__main__ runs (i.e. run_all_tests). with assert_raises(galsim.GalSimIncompatibleValuesError): galsim.wfirst.getPSF(SCAs=use_sca, n_waves=2, approximate_struts=False, high_accuracy=True, From d78da341919aaffccaed4df12f63dbbe0ce3c101 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 4 May 2018 02:58:00 -0400 Subject: [PATCH 70/96] Require 100% codecov going forward (#755) --- .codecov.yml | 4 ++-- .travis.yml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index ebcd2fc3b13..efcb6a70ea7 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,14 +9,14 @@ coverage: status: project: default: - target: 95% + target: 100% threshold: 0% branches: null patch: default: target: 100% - threshold: 1% + threshold: 0% branches: null changes: false diff --git a/.travis.yml b/.travis.yml index 426d88c67a5..c95f91b1697 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ branches: only: - master - noboost - - '#755' language: python python: From 54d3e73486badcb526d38a9aea88b8dced265de6 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sat, 5 May 2018 05:12:27 -0700 Subject: [PATCH 71/96] Fix gcc6 warnings --- SConstruct | 3 +++ include/galsim/Laguerre.h | 3 +++ include/galsim/OneDimensionalDeviate.h | 8 ++++++-- pysrc/Integ.cpp | 8 ++++++++ src/RealGalaxy.cpp | 3 +++ src/WCS.cpp | 3 +++ src/hsm/PSFCorr.cpp | 3 +++ 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/SConstruct b/SConstruct index be8c5c5fe9d..d7970e28786 100644 --- a/SConstruct +++ b/SConstruct @@ -1601,6 +1601,9 @@ PyMODINIT_FUNC initcheck_tmv(void) def CheckEigen(config): eigen_source_file = """ +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wint-in-bool-context" +#endif #include "Python.h" #include "Eigen/Core" #include "Eigen/Cholesky" diff --git a/include/galsim/Laguerre.h b/include/galsim/Laguerre.h index 3f739e989b4..9bf9f4dd3fa 100644 --- a/include/galsim/Laguerre.h +++ b/include/galsim/Laguerre.h @@ -32,6 +32,9 @@ typedef tmv::Matrix MatrixXd; typedef tmv::Vector > VectorXcd; typedef tmv::Matrix > MatrixXcd; #else +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wint-in-bool-context" +#endif #include "Eigen/Dense" using Eigen::VectorXd; using Eigen::MatrixXd; diff --git a/include/galsim/OneDimensionalDeviate.h b/include/galsim/OneDimensionalDeviate.h index 2d4da0180c7..7c64f1c570d 100644 --- a/include/galsim/OneDimensionalDeviate.h +++ b/include/galsim/OneDimensionalDeviate.h @@ -108,7 +108,11 @@ namespace galsim { _xUpper(xUpper), _isRadial(isRadial), _gsparams(gsparams), - _fluxIsReady(false) {} + _fluxIsReady(false), + _useRejectionMethod(false), + _invMaxAbsDensity(0.), + _invMeanAbsDensity(0.) + {} Interval(const Interval& rhs) : _fluxDensityPtr(rhs._fluxDensityPtr), @@ -120,7 +124,7 @@ namespace galsim { _useRejectionMethod(rhs._useRejectionMethod), _invMaxAbsDensity(rhs._invMaxAbsDensity), _invMeanAbsDensity(rhs._invMeanAbsDensity) - {} + {} Interval& operator=(const Interval& rhs) { diff --git a/pysrc/Integ.cpp b/pysrc/Integ.cpp index 5032035e3a0..97a34b53a55 100644 --- a/pysrc/Integ.cpp +++ b/pysrc/Integ.cpp @@ -24,6 +24,11 @@ namespace galsim { namespace integ { +#if defined(__GNUC__) && __GNUC__ >= 6 +// Workaround for a bug in some versions of gcc 6-8. +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947 +#pragma GCC visibility push(hidden) +#endif // A C++ function object that just calls a python function. class PyFunc : public std::unary_function @@ -35,6 +40,9 @@ namespace integ { private: const py::object& _func; }; +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC visibility pop +#endif // Integrate a python function using int1d. py::tuple PyInt1d(const py::object& func, double min, double max, diff --git a/src/RealGalaxy.cpp b/src/RealGalaxy.cpp index d12d1d0c063..2b8e6546214 100644 --- a/src/RealGalaxy.cpp +++ b/src/RealGalaxy.cpp @@ -20,6 +20,9 @@ #ifdef USE_TMV #include "TMV.h" #else +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wint-in-bool-context" +#endif #include "Eigen/Dense" #endif diff --git a/src/WCS.cpp b/src/WCS.cpp index f95c0fb6d95..7bcf23b5df3 100644 --- a/src/WCS.cpp +++ b/src/WCS.cpp @@ -27,6 +27,9 @@ typedef tmv::Vector VectorXd; typedef tmv::Matrix MatrixXd; typedef tmv::VectorView MapVectorXd; #else +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wint-in-bool-context" +#endif #include "Eigen/Dense" using Eigen::VectorXd; using Eigen::MatrixXd; diff --git a/src/hsm/PSFCorr.cpp b/src/hsm/PSFCorr.cpp index 6c1d21cf3b7..f5a682ce8b8 100644 --- a/src/hsm/PSFCorr.cpp +++ b/src/hsm/PSFCorr.cpp @@ -54,6 +54,9 @@ damages of any kind. typedef tmv::Matrix MatrixXd; typedef tmv::Vector VectorXd; #else +#if defined(__GNUC__) && __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wint-in-bool-context" +#endif #include "Eigen/Dense" using Eigen::MatrixXd; using Eigen::VectorXd; From deaa7304229b77eca7f97dc9b92bc2822af42cb0 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 6 May 2018 18:35:58 -0400 Subject: [PATCH 72/96] Use BuildFile rather than Process for des_meds tests to avoid implicit copy. (#755) --- tests/test_des.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_des.py b/tests/test_des.py index 6b74ea2a51f..381d0b9e39b 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -322,13 +322,15 @@ def get_offset(obj_num): import logging logging.basicConfig(format="%(message)s", level=logging.WARN, stream=sys.stdout) logger = logging.getLogger('test_meds_config') - galsim.config.Process(config, logger=logger) + galsim.config.BuildFile(config, logger=logger) # Add in badpix and offset so we run both with and without options. + config = galsim.config.CleanConfig(config) config['image']['offset'] = { 'type' : 'XY' , 'x' : offset_x, 'y' : offset_y } config['output']['badpix'] = {} - galsim.config.Process(config, logger=logger) + galsim.config.BuildFile(config, logger=logger) + # Scattered image is invalid with MEDS output config = galsim.config.CleanConfig(config) config['image'] = { 'type' : 'Scattered', @@ -337,7 +339,7 @@ def get_offset(obj_num): 'size' : stamp_size , } with assert_raises(galsim.GalSimConfigError): - galsim.config.Process(config, logger=logger) + galsim.config.BuildFile(config, logger=logger) # Now repeat, making a separate file for each config = galsim.config.CleanConfig(config) From 9a14ee8420503229dd8bbb440b1ca79974d03cdc Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Sun, 6 May 2018 20:37:09 -0400 Subject: [PATCH 73/96] Add GalSimFFTSizeError (#755) --- galsim/__init__.py | 1 + galsim/errors.py | 23 +++++++++++++++++++++++ galsim/gsobject.py | 22 +++++++++++----------- galsim/phase_psf.py | 10 +++------- tests/test_chromatic.py | 2 +- tests/test_config_gsobject.py | 2 +- tests/test_errors.py | 16 ++++++++++++++++ tests/test_inclined.py | 2 +- tests/test_optics.py | 9 +++------ tests/test_phase_psf.py | 6 +++--- tests/test_second_kick.py | 2 -- tests/test_transforms.py | 3 +-- tests/test_vonkarman.py | 3 +-- tests/test_wfirst.py | 3 +-- 14 files changed, 66 insertions(+), 38 deletions(-) diff --git a/galsim/__init__.py b/galsim/__init__.py index 4852c171112..47fcdc7c445 100644 --- a/galsim/__init__.py +++ b/galsim/__init__.py @@ -106,6 +106,7 @@ from .errors import GalSimKeyError, GalSimIndexError, GalSimNotImplementedError from .errors import GalSimBoundsError, GalSimUndefinedBoundsError, GalSimImmutableError from .errors import GalSimIncompatibleValuesError, GalSimSEDError, GalSimHSMError +from .errors import GalSimFFTSizeError from .errors import GalSimConfigError, GalSimConfigValueError from .errors import GalSimWarning, GalSimDeprecationWarning diff --git a/galsim/errors.py b/galsim/errors.py index 2f13b3e974d..eac90183805 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -112,6 +112,10 @@ # # GalSimHSMError: Use this for errors from the HSM algorithm. # +# GalSimFFTSizeError: Use this when a requested FFT would exceed the relevant maximum_fft_size +# for the object, so the recommendation is raise this parameter if that +# is possible. +# # GalSimConfigError: Use this for errors processing a config dict. # # GalSimConfigValueError: Use this when a config dict has a value that is invalid. Basically, @@ -313,6 +317,25 @@ def __repr__(self): return 'galsim.GalSimHSMError(%r)'%(str(self)) +class GalSimFFTSizeError(GalSimError): + """A GalSim-specific exception class indicating that a requested FFT exceeds the relevant + maximum_fft_size. + """ + def __init__(self, message, size): + self.message = message + self.size = size + mem = size * size * 24. / 1024**3 + message += "\nThe required FFT size would be {0} x {0}, which requires ".format(size) + message += "{0:.2f} GB of memory.\n".format(mem) + message += "If you can handle the large FFT, you may update gsparams.maximum_fft_size." + super().__init__(message) + + def __repr__(self): + return 'galsim.GalSimFFTSizeError(%r,%r)'%(self.message, self.size) + def __reduce__(self): + return GalSimFFTSizeError, (self.message, self.size) + + class GalSimConfigError(GalSimError, ValueError): """A GalSim-specific exception class indicating some kind of failure processing a configuration file. diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 8e5c167d8f2..07be2484a8c 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -59,7 +59,8 @@ from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimNotImplementedError, GalSimWarning, convert_cpp_errors +from .errors import GalSimFFTSizeError, GalSimNotImplementedError +from .errors import GalSimWarning, convert_cpp_errors class GSObject(object): @@ -190,19 +191,20 @@ class GSObject(object): >>> gal = galsim.Sersic(n=4, half_light_radius=4.3) >>> psf = galsim.Moffat(beta=3, fwhm=2.85) >>> conv = galsim.Convolve([gal,psf]) - >>> im = galsim.Image(1000,1000, scale=0.05) # Note the very small pixel scale! + >>> im = galsim.Image(1000,1000, scale=0.02) # Note the very small pixel scale! >>> im = conv.drawImage(image=im) # This uses the default GSParams. Traceback (most recent call last): File "", line 1, in - File "galsim/gsobject.py", line 1615, in drawImage + File "galsim/gsobject.py", line 1666, in drawImage added_photons = prof.drawFFT(draw_image, add) - File "galsim/gsobject.py", line 1827, in drawFFT + File "galsim/gsobject.py", line 1877, in drawFFT kimage, wrap_size = self.drawFFT_makeKImage(image) - File "galsim/gsobject.py", line 1753, in drawFFT_makeKImage - "If you can handle the large FFT, you may update gsparams.maximum_fft_size.") - RuntimeError: drawFFT requires an FFT that is too large: 6144. + File "galsim/gsobject.py", line 1802, in drawFFT_makeKImage + raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk) + galsim.errors.GalSimFFTSizeError: drawFFT requires an FFT that is too large. + The required FFT size would be 12288 x 12288, which requires 3.38 GB of memory. If you can handle the large FFT, you may update gsparams.maximum_fft_size. - >>> big_fft_params = galsim.GSParams(maximum_fft_size=10240) + >>> big_fft_params = galsim.GSParams(maximum_fft_size=12300) >>> conv = galsim.Convolve([gal,psf],gsparams=big_fft_params) >>> im = conv.drawImage(image=im) # Now it works (but is slow!) >>> im.write('high_res_sersic.fits') @@ -1798,9 +1800,7 @@ def drawFFT_makeKImage(self, image): Nk = int(np.ceil(maxk/dk)) * 2 if Nk > self.gsparams.maximum_fft_size: - raise GalSimError( - "drawFFT requires an FFT that is too large: %s. If you can handle " - "the large FFT, you may update gsparams.maximum_fft_size."%(Nk)) + raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk) bounds = _BoundsI(0,Nk//2,-Nk//2,Nk//2) if image.dtype in [ np.complex128, np.float64, np.int32, np.uint32 ]: diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index ee7f2d7873a..d1be4969bfd 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,7 +80,7 @@ from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError -from .errors import GalSimWarning +from .errors import GalSimFFTSizeError, GalSimWarning class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -325,9 +325,7 @@ def _generate_pupil_plane(self, circular_pupil, nstruts, strut_thick, strut_angl # Check FFT size if self.npix > self._gsparams.maximum_fft_size: - raise GalSimError("Created pupil plane array that is too large, {0} " - "If you can handle the large FFT, you may update " - "gsparams.maximum_fft_size".format(self.npix)) + raise GalSimFFTSizeError("Created pupil plane array that is too large.",self.npix) self.pupil_plane_size = pupil_plane_size # Shrink scale such that size = scale * npix exactly. @@ -391,9 +389,7 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good # Check FFT size if self.npix > self._gsparams.maximum_fft_size: - raise GalSimError("Loaded pupil plane array that is too large, {0} " - "If you can handle the large FFT, you may update " - "gsparams.maximum_fft_size".format(self.npix)) + raise GalSimFFTSizeError("Loaded pupil plane array that is too large.", self.npix) # Sanity checks if pupil_plane_im.array.shape[0] != pupil_plane_im.array.shape[1]: diff --git a/tests/test_chromatic.py b/tests/test_chromatic.py index 60c8c36c457..adf204c6d18 100644 --- a/tests/test_chromatic.py +++ b/tests/test_chromatic.py @@ -1269,7 +1269,7 @@ def test_gsparam(): # getting properly forwarded through the internals of ChromaticObjects. gsparams = galsim.GSParams(maximum_fft_size=16) gal = galsim.Gaussian(fwhm=1, gsparams=gsparams) * bulge_SED - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimFFTSizeError): gal.drawImage(bandpass) # Repeat, putting the gsparams argument in after the ChromaticObject constructor. diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index 9ab72d32bff..ab2d3e6bf9d 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -539,7 +539,7 @@ def test_sersic(): # and would be rather slow. gal6a = galsim.config.BuildGSObject(config, 'gal6')[0] gal6b = galsim.Sersic(n=0.7, half_light_radius=1, flux=50) - with assert_raises(galsim.GalSimError): + with assert_raises(galsim.GalSimFFTSizeError): gsobject_compare(gal6a, gal6b, conv=galsim.Gaussian(sigma=1)) gal7a = galsim.config.BuildGSObject(config, 'gal7')[0] diff --git a/tests/test_errors.py b/tests/test_errors.py index f06925faf08..1dd7a7ef9de 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -205,6 +205,21 @@ def test_galsim_hsm_error(): do_pickle(err) +@timer +def test_galsim_fft_size_error(): + """Test basic usage of GalSimFFTSizeError + """ + err = galsim.GalSimFFTSizeError("Test FFT is too big.", 10240) + print('str = ',str(err)) + print('repr = ',repr(err)) + assert str(err) == ("Test FFT is too big.\nThe required FFT size would be 10240 x 10240, " + "which requires 2.34 GB of memory.\nIf you can handle " + "the large FFT, you may update gsparams.maximum_fft_size.") + assert err.size == 10240 + assert isinstance(err, galsim.GalSimError) + do_pickle(err) + + @timer def test_galsim_config_error(): """Test basic usage of GalSimConfigError @@ -295,6 +310,7 @@ def test_galsim_deprecation_warning(): test_galsim_incompatible_values_error() test_galsim_sed_error() test_galsim_hsm_error() + test_galsim_fft_size_error() test_galsim_config_error() test_galsim_config_value_error() test_galsim_notimplemented_error() diff --git a/tests/test_inclined.py b/tests/test_inclined.py index 15e699890cb..14ef7dd2e54 100644 --- a/tests/test_inclined.py +++ b/tests/test_inclined.py @@ -335,7 +335,7 @@ def test_edge_on(): for inclination in inclinations: # Set up the profile prof = get_prof(mode, inclination * galsim.radians, scale_radius=scale_radius, - scale_h_over_r=0.1, n=n, gsparams=galsim.GSParams(maximum_fft_size=5132)) + scale_h_over_r=0.1, n=n) check_basic(prof, "Edge-on " + mode) diff --git a/tests/test_optics.py b/tests/test_optics.py index 46c287e61fa..9056abeea97 100644 --- a/tests/test_optics.py +++ b/tests/test_optics.py @@ -580,12 +580,10 @@ def test_OpticalPSF_pupil_plane(): # Supply the pupil plane at higher resolution, and make sure that the routine figures out the # sampling and gets the right image scale etc. - gsp = galsim.GSParams(maximum_fft_size=8192) rescale_fac = 0.77 ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts, strut_angle=strut_angle, oversampling=pp_oversampling, - pad_factor=pp_pad_factor/rescale_fac, - gsparams=gsp) + pad_factor=pp_pad_factor/rescale_fac) # Make higher resolution pupil plane image via interpolation int_im = galsim.InterpolatedImage(galsim.Image(im, scale=1.0, dtype=np.float32), calculate_maxk=False, calculate_stepk=False, @@ -593,8 +591,7 @@ def test_OpticalPSF_pupil_plane(): new_im = int_im.drawImage(scale=rescale_fac, method='no_pixel') new_im.wcs = None # Let OpticalPSF figure out the scale automatically. test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, - pupil_plane_im=new_im, oversampling=pp_oversampling, - gsparams=gsp) + pupil_plane_im=new_im, oversampling=pp_oversampling) im_ref_psf = ref_psf.drawImage(scale=scale) im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1]) im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale) @@ -630,7 +627,7 @@ def test_OpticalPSF_pupil_plane(): big_im[im.bounds] = im test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=big_im, oversampling=pp_oversampling, - pad_factor=pp_pad_factor, gsparams=gsp) + pad_factor=pp_pad_factor) im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1]) im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale) test_moments = im_test_psf.FindAdaptiveMom() diff --git a/tests/test_phase_psf.py b/tests/test_phase_psf.py index 1943dfb28c3..a7f33c37217 100644 --- a/tests/test_phase_psf.py +++ b/tests/test_phase_psf.py @@ -74,12 +74,12 @@ def test_aperture(): np.testing.assert_almost_equal(maxk, np.pi/scale) # If the constructed pupil plane would be too large, raise an error - assert_raises(galsim.GalSimError, galsim.Aperture, 1.7, pupil_plane_scale=1.e-4) + assert_raises(galsim.GalSimFFTSizeError, galsim.Aperture, 1.7, pupil_plane_scale=1.e-4) # Similar if the given image is too large. # Here, we change gsparams.maximum_fft_size, rather than build a really large image to load. - assert_raises(galsim.GalSimError, galsim.Aperture, 1.7, pupil_plane_im=im, - gsparams=galsim.GSParams(maximum_fft_size=64)) + with assert_raises(galsim.GalSimFFTSizeError): + galsim.Aperture(1.7, pupil_plane_im=im, gsparams=galsim.GSParams(maximum_fft_size=64)) # Other choices just give warnings with assert_warns(galsim.GalSimWarning): diff --git a/tests/test_second_kick.py b/tests/test_second_kick.py index fa2e7e409ee..dd4370e1637 100644 --- a/tests/test_second_kick.py +++ b/tests/test_second_kick.py @@ -29,7 +29,6 @@ def test_init(): """Test generation of SecondKick profiles """ obscuration = 0.5 - bigGSP = galsim.GSParams(maximum_fft_size=8192) if __name__ == '__main__': lams = [300.0, 500.0, 1100.0] @@ -46,7 +45,6 @@ def test_init(): t0 = time.time() kwargs = {'lam':lam, 'r0':r0, 'kcrit':kcrit, 'diam':4.0} print(kwargs) - kwargs['gsparams'] = bigGSP sk = galsim.SecondKick(flux=2.2, **kwargs) t1 = time.time() diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 0819489f44b..b90205358e9 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -633,8 +633,7 @@ def test_flip(): gsparams=galsim.GSParams(realspace_relerr=1.e-6)), # Without being convolved by anything with a reasonable k cutoff, this needs # a very large fft. - galsim.DeVaucouleurs(half_light_radius=0.17, flux=1.7, - gsparams=galsim.GSParams(maximum_fft_size=8000)), + galsim.DeVaucouleurs(half_light_radius=0.17, flux=1.7), # I don't really understand why this needs a lower maxk_threshold to work, but # without it, the k-space tests fail. galsim.Exponential(scale_radius=0.17, flux=1.7, diff --git a/tests/test_vonkarman.py b/tests/test_vonkarman.py index a7e1e63cd94..83391c6da2e 100644 --- a/tests/test_vonkarman.py +++ b/tests/test_vonkarman.py @@ -29,7 +29,6 @@ def test_vk(slow=False): """Test the generation of VonKarman profiles """ - gsp = galsim.GSParams(maximum_fft_size=8192) if slow: lams = [300.0, 500.0, 1100.0] r0_500s = [0.05, 0.15, 0.3] @@ -52,7 +51,7 @@ def test_vk(slow=False): print("Skip this combination, since delta > maxk_threshold") continue - vk = galsim.VonKarman(flux=2.2, gsparams=gsp, **kwargs) + vk = galsim.VonKarman(flux=2.2, **kwargs) np.testing.assert_almost_equal(vk.flux, 2.2) check_basic(vk, "VonKarman") diff --git a/tests/test_wfirst.py b/tests/test_wfirst.py index 56b442adbe2..d2ab92c4bbc 100644 --- a/tests/test_wfirst.py +++ b/tests/test_wfirst.py @@ -703,8 +703,7 @@ def test_wfirst_psfs(): { 'approximate_struts':False, 'high_accuracy':False }, # This last test works, but it takes ~10 min to run. So even in the slow tests, # this is a bit too extreme. - #{ 'approximate_struts':False, 'high_accuracy':True, - # 'gsparams':galsim.GSParams(maximum_fft_size=8192) } + #{ 'approximate_struts':False, 'high_accuracy':True, } ]: psf = galsim.wfirst.getPSF(SCAs=use_sca, **kwargs)[use_sca] From 745f54817ada82a58adb8c2cba440e004bb82d1a Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 7 May 2018 09:25:58 -0400 Subject: [PATCH 74/96] Typos, word choice, etc. (#755) --- galsim/errors.py | 112 ++++++++++++++++++++++++----------------- galsim/fits.py | 4 +- galsim/photon_array.py | 5 +- tests/test_errors.py | 1 + 4 files changed, 72 insertions(+), 50 deletions(-) diff --git a/galsim/errors.py b/galsim/errors.py index eac90183805..22be6a8ffd9 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -28,40 +28,39 @@ # throw the following in some cases. # # TypeError: Use this for errors that in a more strongly typed language would probably -# be a compiler error. For instance, it is used for the following errors: -# - a parameter has the wrong type +# be a compiler error. For instance, it is used for the following errors: +# - a parameter with the wrong type # - the wrong number of unnamed args when processing `*args` by hand. # - missing or invalid kwargs when processing `**kwargs` by hand. # -# OSError: Use this for errors related to I/O, disk access, etc. Note: In Python 2, -# there was a distinction between IOError and OSError, but there was never -# much difference in reality, and in Python 3, they made everything OSError. -# We should just use OSError for all such kinds of errors. +# OSError: Use this for errors related to I/O, disk access, etc. +# Note: In Python 2, there was a distinction between IOError and OSError, but +# there was never much difference in reality, and in Python 3, they made both +# OSError. We should just use OSError for all such kinds of errors. # # NotImplementedError: Use this for code that is not implemented by design and which will never -# be implemented. E.g. GSObject and Position use this for their __init__ +# be implemented. E.g. GSObject and Position use this for their __init__ # implementations, since it is invalid to instantiate the base class. -# Use GalSimNotImplementedError for features which might someday be +# Use GalSimNotImplementedError for features that might someday be # implemented. # # AttributeError: Use this only for an attempt to access an attribute that an object does not -# have. Like TypeError, this should be reserved for things which a more +# have. Like TypeError, this should be reserved for things that a more # strongly typed language would catch at compile time. We don't currently # raise this anywhere in GalSim. # -# RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. +# RuntimeError: Don't use this. Use GalSimError (or a subclass) for any run-time errors. # -# ValueError: Don't use this. Use one of the below exceptions that derive from -# ValueError. +# ValueError: Don't use this. Use one of the below exceptions that derive from ValueError. # -# KeyError: Don't use this. Use GalSimKeyError instead +# KeyError: Don't use this. Use GalSimKeyError instead # -# IndexError: Don't use this. Use GalSimIndexError instead. +# IndexError: Don't use this. Use GalSimIndexError instead. # # std::runtime_error: Use this for errors in the C++ layer, and use the catch_cpp_errors() -# context to convert these errors into GalSimErrors. E.g. -# GSFitsWCS._invert_pv uses this for non-convergence, which is converted -# into a GalSimError in Python. +# context to convert these errors into GalSimErrors. E.g. GSFitsWCS._invert_pv +# uses this for non-convergence, which gets converted into GalSimError in +# the Python layer. # When possible, it is preferable to guard against any such events by making # appropriate checks in the Python layer before dropping down into C++. # E.g. Image checks for anything that might cause the C++ Image class to @@ -72,12 +71,14 @@ # GalSim-specific error classes: # ------------------------------ # -# GalSimError: Use this for what would normally be a RuntimeError. Usually some -# exceptional occurrence in otherwise correct code. E.g. an algorithm not -# converging, or some invalid data values. This is also the catch-all -# exception to use when none of the other GalSim exceptions are appropriate. +# GalSimError: Use this for what would normally be a RuntimeError. Usually some exceptional +# occurrence in otherwise correct code. E.g. an algorithm not converging or +# a singular matrix encountered. It can also be used when the program does +# things out of order; e.g. PowerSpectrum raises this when getShear and the +# like are called before `buildGrid`. This is also the catch-all exception +# to use when none of the other GalSim exceptions are appropriate. # -# GalSimValueError: Use this for when a user provides an invalid value for a parameter. +# GalSimValueError: Use this when a user provides an invalid value for a parameter. # Note: it has an optional argument to give a list of allowed values when # that is appropriate. # @@ -88,29 +89,35 @@ # invalid index. E.g. RealGalaxyCatalog and Catalog raise this for accessing # invalid rows. # -# GalSimRangeError: Use this when a a user provides an value outside of some allowed range. -# You should also give the min/max values of the allowed range. The max -# is optional, because it's not uncommon for their to be no upper limit. +# GalSimRangeError: Use this when a user provides an value outside of some allowed range. +# You should also give the min/max values of the allowed range. The max +# is optional, because it's not uncommon for there to be no upper limit. # If only the upper limit is relevant and not the lower limit, you may # use min=None to indicate this. # -# GalSimBoundsError: Use this when a position is outside the allowed bounds. It's basically +# GalSimBoundsError: Use this when a position is outside its allowed bounds. It's basically # the same as GalSimRangeError, but in two dimensions. # # GalSimUndefinedBoundsError: Use this when the user tries to performa an operation on an -# Image with undefined bounds that requires the bounds to be -# defined. +# Image with undefined bounds (and which requires the bounds to be +# defined). # # GalSimImmutableError: Use this when the user tries to modify an immutable Image in some way. # # GalSimIncompatibleValuesError: Use this when two or more parameters are invalid when used -# in combination. E.g. providing more than one size parameter -# to Moffat, Sersic, Gaussian, etc. +# in combination. E.g. providing more than one size parameter +# to Moffat, Sersic, Gaussian, etc. The conflicting values +# should be given as extra keywords to the constructor, which +# are mentioned in the error message. +# Note: if one of the conflicting values is self (e.g. adding two +# SEDs with different redshifts), then don't name the kwarg self. +# Instead use something like `self_sed=self`. # -# GalSimSEDError: Use this when an SED is required to be either spectral or dimensionless -# and the wrong kind of SED is provided. +# GalSimSEDError: Use this when an SED is required to be either spectral or dimensionless, +# and the other kind of SED is provided. # -# GalSimHSMError: Use this for errors from the HSM algorithm. +# GalSimHSMError: Use this for errors from the HSM algorithm. They are emitted in C++, but +# we use `with convert_cpp_errors(GalSimHSMError):` to convert them. # # GalSimFFTSizeError: Use this when a requested FFT would exceed the relevant maximum_fft_size # for the object, so the recommendation is raise this parameter if that @@ -118,12 +125,12 @@ # # GalSimConfigError: Use this for errors processing a config dict. # -# GalSimConfigValueError: Use this when a config dict has a value that is invalid. Basically, +# GalSimConfigValueError: Use this when a config dict has a value that is invalid. Basically, # whenever you would normally use GalSimValueError when processing # a config dict, you should use this instead. # # GalSimNotImplementedError Use this for features that we have not yet implemented, but which may -# be implemented someday. So it's not a necessarily invalid usage, just +# be implemented someday. So it's not a necessarily invalid usage, just # something that doesn't work currently. class GalSimError(RuntimeError): @@ -138,7 +145,7 @@ def __hash__(self): return hash(repr(self)) class GalSimValueError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input value is invalid. - Attrubutes: + Attributes: value = the invalid value allowed_values = a list of allowed values if appropriate (may be None) @@ -162,6 +169,10 @@ def __reduce__(self): # Need to override this whenever constructor take extra p class GalSimKeyError(GalSimError, KeyError): """A GalSim-specific exception class indicating an attempt to access a dict-like object with an invalid key. + + Attributes: + + key = the invalid key """ def __init__(self, message, key): self.message = message @@ -178,6 +189,10 @@ def __repr__(self): class GalSimIndexError(GalSimError, IndexError): """A GalSim-specific execption class indicating an attempt to access a list-like object with an invalid index. + + Attributes: + + index = the invalid index """ def __init__(self, message, index): self.message = message @@ -195,7 +210,7 @@ class GalSimRangeError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input value is outside of the allowed range of values. - Attrubutes: + Attributes: value = the invalid value min = the minimum allowed value (may be None) @@ -220,7 +235,7 @@ class GalSimBoundsError(GalSimError, ValueError): """A GalSim-specific exception class indicating that some user-input position is outside of the allowed bounds. - Attrubutes: + Attributes: pos = the invalid position bounds = the bounds in which it was expected to fall @@ -240,8 +255,8 @@ def __reduce__(self): class GalSimUndefinedBoundsError(GalSimError): - """A GalSim-specific exception class indicating an attempt to access the range of bounds - that have not yet been defined. + """A GalSim-specific exception class indicating an attempt to access the extent of + a Bounds instance that has not yet been defined. """ def __repr__(self): return 'galsim.GalSimUndefinedBoundsError(%r)'%(str(self)) @@ -250,7 +265,7 @@ def __repr__(self): class GalSimImmutableError(GalSimError): """A GalSim-specific exception class indicating an attempt to modify an immutable image. - Attrubutes: + Attributes: image = the image that the user attempted to modify """ @@ -271,7 +286,7 @@ class GalSimIncompatibleValuesError(GalSimError, ValueError, TypeError): """A GalSim-specific exception class indicating that 2 or more user-input values are incompatible as given. - Attrubutes: + Attributes: values = a dict of {name : value} giving the values that in combination are invalid. """ @@ -293,7 +308,7 @@ class GalSimSEDError(GalSimError, TypeError): kind of SED that is present. Typically involving a dimensionless SED where a spectral SED is required (or vice versa). - Attrubutes: + Attributes: sed = the invalid SED """ @@ -320,13 +335,18 @@ def __repr__(self): class GalSimFFTSizeError(GalSimError): """A GalSim-specific exception class indicating that a requested FFT exceeds the relevant maximum_fft_size. + + Attributes: + + size = the size that was deemed too large + mem = the estimated memory that would be required (in GB) for the FFT. """ def __init__(self, message, size): self.message = message self.size = size - mem = size * size * 24. / 1024**3 + self.mem = size * size * 24. / 1024**3 message += "\nThe required FFT size would be {0} x {0}, which requires ".format(size) - message += "{0:.2f} GB of memory.\n".format(mem) + message += "{0:.2f} GB of memory.\n".format(self.mem) message += "If you can handle the large FFT, you may update gsparams.maximum_fft_size." super().__init__(message) @@ -347,7 +367,7 @@ def __repr__(self): class GalSimConfigValueError(GalSimValueError, GalSimConfigError): """A GalSim-specific exception class indicating that a config entry has an invalid value. - Attrubutes: + Attributes: value = the invalid value allowed_values = a list of allowed values if appropriate (may be None) diff --git a/galsim/fits.py b/galsim/fits.py index 4a04899d70b..2e01336fd0c 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -291,7 +291,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) self.gz_index += 1 self.gz = self.gz_methods[self.gz_index] else: # pragma: no cover - raise GalSimError("None of the options for gunzipping were successful.") + raise GalSimError("None of the options for gzipping were successful.") elif file_compress == 'bzip2': while self.bz2_index < len(self.bz2_methods): try: @@ -303,7 +303,7 @@ def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress) self.bz2_index += 1 self.bz2 = self.bz2_methods[self.bz2_index] else: # pragma: no cover - raise GalSimError("None of the options for bunzipping were successful.") + raise GalSimError("None of the options for bzipping were successful.") else: # pragma: no cover (can't get here from public API) raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2')) diff --git a/galsim/photon_array.py b/galsim/photon_array.py index f33e9f9f6a5..1531ff5e741 100644 --- a/galsim/photon_array.py +++ b/galsim/photon_array.py @@ -26,7 +26,7 @@ from .random import UniformDeviate from .angle import radians, arcsec from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError -from .errors import convert_cpp_errors +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors # Add on more methods in the python layer @@ -212,7 +212,8 @@ def assignAt(self, istart, rhs): def convolve(self, rhs, rng=None): "Convolve this PhotonArray with another." if rhs.size() != self.size(): - raise GalSimError("PhotonArray.convolve with unequal size arrays") + raise GalSimIncompatibleValuesError("PhotonArray.convolve with unequal size arrays", + self_pa=self, rhs=rhs) ud = UniformDeviate(rng) self._pa.convolve(rhs._pa, ud._rng) diff --git a/tests/test_errors.py b/tests/test_errors.py index 1dd7a7ef9de..96a5f1c1735 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -216,6 +216,7 @@ def test_galsim_fft_size_error(): "which requires 2.34 GB of memory.\nIf you can handle " "the large FFT, you may update gsparams.maximum_fft_size.") assert err.size == 10240 + np.testing.assert_almost_equal(err.mem, 2.34375) assert isinstance(err, galsim.GalSimError) do_pickle(err) From 833fe39046833cfde524702b9a5d981af7173fbe Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Mon, 7 May 2018 10:32:29 -0400 Subject: [PATCH 75/96] Don't cover check for race condition in lock.acquire() (#755) --- galsim/real.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galsim/real.py b/galsim/real.py index 22b25e0db86..c594e7338a1 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -769,7 +769,7 @@ def getNoiseProperties(self, i): else: self.noise_lock.acquire() # Again, a second check in case two processes get here at the same time. - if self.noise_file_name[i] in self.saved_noise_im: + if self.noise_file_name[i] in self.saved_noise_im: # pragma: no cover im = self.saved_noise_im[self.noise_file_name[i]] if self.logger: self.logger.debug('RealGalaxyCatalog %d: Got saved noise im',i) From ed40205739b78cbabecea49631327e587426ed41 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 10 May 2018 20:16:41 -0400 Subject: [PATCH 76/96] Move maximum_fft_size item in CHANGELOG to API change (#755) --- CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c62b4cdaaa..a05bac336a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Changes from v1.6 to v2.0 The principal change in GalSim 2.0 is that it is now pip installable. See the updated INSTALL file for details on how to install GalSim using -either pip or setup.py. +either pip or setup.py. The functionality is essentially equivalent to +v1.6, although there are a few (mostly minor) API changes in some classes. Dependency Changes ------------------ @@ -30,6 +31,9 @@ Dependency Changes API Changes ----------- +- Changed the default maximum_fft_size in GSParams to 8192 from 4096. This + increases the potential memory used by an FFT when drawing an object with + an FFT from 256 MB to 1 GB. (#755) - Changed the order of arguments of galsim.wfirst.allDetectorEffects. (#755) - Most of the functionality associated with C++-layer objects has been redesigned or removed. These were non-public-API features, so if you have @@ -86,6 +90,3 @@ New Features parameters outside of allowed ranges. (#755) - Changed the type of warnings raised by GalSim to GalSimWarning, which is a subclass of UserWarning. (#755) -- Changed the default maximum_fft_size in GSParams to 8192 from 4096. This - increases the potential memory used by an FFT when drawing an object with - an FFT from 256 MB to 1 GB. (#755) From af74a8961a65a9004abe829d00929402d955d785 Mon Sep 17 00:00:00 2001 From: Josh Meyers Date: Wed, 16 May 2018 14:44:22 -0700 Subject: [PATCH 77/96] typos (#755) --- galsim/chromatic.py | 2 +- galsim/config/extra.py | 4 ++-- galsim/errors.py | 10 +++++----- galsim/interpolant.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/galsim/chromatic.py b/galsim/chromatic.py index a6da67368e8..3a536132597 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -1153,7 +1153,7 @@ def _get_interp_image(self, bandpass, image=None, integrator='trapezoidal', if _flux_ratio is None: _flux_ratio = lambda w: 1.0 - # Constant flux_ratio is alread an SED at this point, so can treat as function. + # Constant flux_ratio is already an SED at this point, so can treat as function. assert hasattr(_flux_ratio, '__call__') # setup output image (semi-arbitrarily using the bandpass effective wavelength). diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 40e3ea433c6..0afb80133c4 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -211,7 +211,7 @@ def WriteExtraOutputs(config, main_data, logger=None): if 'file_name' in field: galsim.config.SetDefaultExt(field, '.fits') file_name = galsim.config.ParseValue(field,'file_name',config,str)[0] - else: # pragma: no cover (it is convered, but codecov wrongly thinks it isn't. + else: # pragma: no cover (it is covered, but codecov wrongly thinks it isn't.) # If no file_name, then probably writing to hdu continue if 'dir' in field: @@ -271,7 +271,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): field = output[key] if 'hdu' in field: hdu = galsim.config.ParseValue(field,'hdu',config,int)[0] - else: # pragma: no cover (it is convered, but codecov wrongly thinks it isn't. + 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: diff --git a/galsim/errors.py b/galsim/errors.py index 22be6a8ffd9..e3c31ccc474 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -98,7 +98,7 @@ # GalSimBoundsError: Use this when a position is outside its allowed bounds. It's basically # the same as GalSimRangeError, but in two dimensions. # -# GalSimUndefinedBoundsError: Use this when the user tries to performa an operation on an +# GalSimUndefinedBoundsError: Use this when the user tries to perform an operation on an # Image with undefined bounds (and which requires the bounds to be # defined). # @@ -129,9 +129,9 @@ # whenever you would normally use GalSimValueError when processing # a config dict, you should use this instead. # -# GalSimNotImplementedError Use this for features that we have not yet implemented, but which may -# be implemented someday. So it's not a necessarily invalid usage, just -# something that doesn't work currently. +# GalSimNotImplementedError: Use this for features that we have not yet implemented, but which may +# be implemented someday. So it's not a necessarily invalid usage, just +# something that doesn't work currently. class GalSimError(RuntimeError): """The base class for GalSim-specific run-time errors. @@ -187,7 +187,7 @@ def __repr__(self): class GalSimIndexError(GalSimError, IndexError): - """A GalSim-specific execption class indicating an attempt to access a list-like object + """A GalSim-specific exception class indicating an attempt to access a list-like object with an invalid index. Attributes: diff --git a/galsim/interpolant.py b/galsim/interpolant.py index 182f70b9cdf..8a2c59a6fcc 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -36,7 +36,7 @@ class Interpolant(object): """ def __init__(self): raise NotImplementedError( - "The Interpolant bas class should not be instantiated directly. " + "The Interpolant base class should not be instantiated directly. " "Use one of the subclasses instead, or use the `from_name` factory function.") @staticmethod From f06cfb52d175fa6c57925f9b78eed679dc79f0a5 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 16 May 2018 23:46:34 -0400 Subject: [PATCH 78/96] Update text about GalSimError in CHANGELOG (#755) --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a05bac336a4..c05ac480858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,10 +83,12 @@ New Features ------------ - Added a new class hierarchy for exceptions raised by GalSim with the base - class `GalSimError`, a subclass of `RuntimeError`. This provides a hook for - adding sub-classes, which may provide more specific information about the - nature of an error. So far, sub-classes include GalSimHSMError for errors - during HSM measurements and GalSimRangeError for attempted use of input - parameters outside of allowed ranges. (#755) + class `GalSimError`, a subclass of `RuntimeError`. For complete details + about the various sub-classes within this hierarchy, see the file errors.py. + In most cases, if you were catching a specific exception such as ValueError + or RuntimeError, the new error will still be caught properly. However, some + cases have changed to an incompatible error type, so users who have written + `except` statements with specific error types should be careful to make + sure that the errors you wanted to catch are still being caught. (#755) - Changed the type of warnings raised by GalSim to GalSimWarning, which is a subclass of UserWarning. (#755) From 0fbc894fd14317f9edf38c3f84b8541accd5597b Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 16 May 2018 23:47:50 -0400 Subject: [PATCH 79/96] Switch EnsureDir to ensure_dir to match more common style in utilities.py (#755) --- galsim/config/extra.py | 2 +- galsim/config/output.py | 4 ++-- galsim/download_cosmos.py | 4 ++-- galsim/utilities.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index 0afb80133c4..c475e364371 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -222,7 +222,7 @@ def WriteExtraOutputs(config, main_data, logger=None): if dir is not None: file_name = os.path.join(dir,file_name) - galsim.config.EnsureDir(file_name) + galsim.config.ensure_dir(file_name) if noclobber and os.path.isfile(file_name): logger.warning('Not writing %s file %d = %s because output.noclobber = True ' diff --git a/galsim/config/output.py b/galsim/config/output.py index ec22d38ca8f..ce3f07ddd3e 100644 --- a/galsim/config/output.py +++ b/galsim/config/output.py @@ -20,7 +20,7 @@ import galsim import logging -from ..utilities import EnsureDir +from ..utilities import ensure_dir # This file handles building the output files according to the specifications in config['output']. # This file includes the basic functionality, but it calls out to helper functions for the @@ -433,7 +433,7 @@ def getFilename(self, config, base, logger): dir = galsim.config.ParseValue(config, 'dir', base, str)[0] file_name = os.path.join(dir,file_name) - EnsureDir(file_name) + ensure_dir(file_name) return file_name diff --git a/galsim/download_cosmos.py b/galsim/download_cosmos.py index 8dcacea8ea7..4f51713917a 100644 --- a/galsim/download_cosmos.py +++ b/galsim/download_cosmos.py @@ -27,7 +27,7 @@ except: from urllib.request import urlopen -from .utilities import EnsureDir +from .utilities import ensure_dir script_name = 'galsim_download_cosmos' @@ -168,7 +168,7 @@ def download(url, target, unpack_dir, args, logger): logger.info("Size of %s: %d MBytes" , file_name, file_size/1024**2) # Make sure the directory we want to put this file exists. - EnsureDir(target) + ensure_dir(target) # Check if the file already exists and if it is the right size do_download = True diff --git a/galsim/utilities.py b/galsim/utilities.py index 1b2fbb80cf1..1ebd1428eac 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -1468,7 +1468,7 @@ def __call__(self, *args): raise TypeError('Method called on dead object') return self.f(self.c(), *args) -def EnsureDir(target): +def ensure_dir(target): """ Make sure the directory for the target location exists, watching for a race condition From 8e9631befca185740fb36b7a98f9a93d49c0582e Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 16 May 2018 23:51:49 -0400 Subject: [PATCH 80/96] Use convert_cpp_errors for InterpolatedImage._shoot (#755) --- galsim/errors.py | 2 +- galsim/interpolatedimage.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/galsim/errors.py b/galsim/errors.py index e3c31ccc474..f6707748339 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -57,7 +57,7 @@ # # IndexError: Don't use this. Use GalSimIndexError instead. # -# std::runtime_error: Use this for errors in the C++ layer, and use the catch_cpp_errors() +# std::runtime_error: Use this for errors in the C++ layer, and use the convert_cpp_errors() # context to convert these errors into GalSimErrors. E.g. GSFitsWCS._invert_pv # uses this for non-convergence, which gets converted into GalSimError in # the Python layer. diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index fcb60a7d869..0d9d99c0ea9 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -674,11 +674,8 @@ def _kValue(self, kpos): @doc_inherit def _shoot(self, photons, ud): - try: + with convert_cpp_errors(): self._sbp.shoot(photons._pa, ud._rng) - except RuntimeError: - xi = self.x_interpolant.__class__.__name__ - raise GalSimError("Photon shooting is not practical with x_interpolant of type %s"%xi) @doc_inherit def _drawReal(self, image): From eeeb63f19edba160b06d0a7156bdd6349184291f Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 16 May 2018 23:52:42 -0400 Subject: [PATCH 81/96] Use tuples rather than lists when used in immutable context (#755) --- galsim/chromatic.py | 11 +++++------ galsim/config/extra.py | 12 ++++++------ galsim/config/stamp.py | 2 +- galsim/config/value.py | 6 +++--- galsim/config/wcs.py | 2 +- galsim/des/des_meds.py | 4 ++-- galsim/fitswcs.py | 4 ++-- galsim/gsobject.py | 16 ++++++++-------- galsim/image.py | 8 ++++---- galsim/interpolant.py | 2 +- galsim/interpolatedimage.py | 2 +- galsim/phase_psf.py | 2 +- galsim/scene.py | 4 ++-- galsim/sed.py | 2 +- galsim/table.py | 2 +- 15 files changed, 39 insertions(+), 40 deletions(-) diff --git a/galsim/chromatic.py b/galsim/chromatic.py index 3a536132597..d080ddd56be 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -865,7 +865,7 @@ def lens(self, g1, g2, mu): @returns the lensed object. """ from .shear import Shear - if any(hasattr(g, '__call__') for g in [g1,g2]): + if any(hasattr(g, '__call__') for g in (g1,g2)): _g1 = g1 _g2 = g2 if not hasattr(g1, '__call__'): _g1 = lambda w: g1 @@ -918,7 +918,7 @@ def transform(self, dudx, dudy, dvdx, dvdy): @returns the transformed object. """ from .transform import Transform - if any(hasattr(dd, '__call__') for dd in [dudx, dudy, dvdx, dvdy]): + if any(hasattr(dd, '__call__') for dd in (dudx, dudy, dvdx, dvdy)): _dudx = dudx _dudy = dudy _dvdx = dvdx @@ -1518,7 +1518,7 @@ def __eq__(self, other): return False # There's really no good way to check that two callables are equal, except if they literally # point to the same object. So we'll just check for that for _jac, _offset, _flux_ratio. - for attr in ['_jac', '_offset', '_flux_ratio']: + for attr in ('_jac', '_offset', '_flux_ratio'): selfattr = getattr(self, attr) otherattr = getattr(other, attr) # For this attr, either both need to be chromatic or neither. @@ -1537,10 +1537,9 @@ def __eq__(self, other): def __hash__(self): # This one's a bit complicated, so we'll go ahead and cache the hash. if not hasattr(self, '_hash'): - self._hash = hash(("galsim.ChromaticTransformation", self.original, self._flux_ratio, - self.gsparams)) + self._hash = hash(("galsim.ChromaticTransformation", self.original, self.gsparams)) # achromatic _jac and _offset are ndarrays, so need to be handled separately. - for attr in ['_jac', '_offset']: + for attr in ('_jac', '_offset', '_flux_ratio'): selfattr = getattr(self, attr) if hasattr(selfattr, '__call__'): self._hash ^= hash(selfattr) diff --git a/galsim/config/extra.py b/galsim/config/extra.py index c475e364371..9b72d29db38 100644 --- a/galsim/config/extra.py +++ b/galsim/config/extra.py @@ -117,7 +117,7 @@ def SetupExtraOutputsForImage(config, logger=None): if 'output' in config: if 'extra_builder' not in config: SetupExtraOutput(config, logger) - for key in [ k for k in valid_extra_outputs.keys() if k in config['output'] ]: + for key in (k for k in valid_extra_outputs.keys() if k in config['output']): builder = config['extra_builder'][key] field = config['output'][key] builder.setupImage(field, config, logger) @@ -135,7 +135,7 @@ def ProcessExtraOutputsForStamp(config, skip, logger=None): """ if 'output' in config: obj_num = config['obj_num'] - for key in [ k for k in valid_extra_outputs.keys() if k in config['output'] ]: + for key in (k for k in valid_extra_outputs.keys() if k in config['output']): builder = config['extra_builder'][key] field = config['output'][key] if skip: @@ -154,7 +154,7 @@ def ProcessExtraOutputsForImage(config, logger=None): """ if 'output' in config: obj_nums = None - for key in [ k for k in valid_extra_outputs.keys() if k in config['output'] ]: + for key in (k for k in valid_extra_outputs.keys() if k in config['output']): image_num = config.get('image_num',0) start_image_num = config.get('start_image_num',0) if obj_nums is None: @@ -206,7 +206,7 @@ def WriteExtraOutputs(config, main_data, logger=None): if 'extra_last_file' not in config: config['extra_last_file'] = {} - for key in [ k for k in valid_extra_outputs.keys() if k in output ]: + for key in (k for k in valid_extra_outputs.keys() if k in output): field = output[key] if 'file_name' in field: galsim.config.SetDefaultExt(field, '.fits') @@ -267,7 +267,7 @@ def AddExtraOutputHDUs(config, main_data, logger=None): """ output = config['output'] hdus = {} - for key in [ k for k in valid_extra_outputs.keys() if k in output ]: + 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] @@ -300,7 +300,7 @@ def CheckNoExtraOutputHDUs(config, output_type, logger=None): """ logger = galsim.config.LoggerWrapper(logger) output = config['output'] - for key in [ k for k in valid_extra_outputs.keys() if k in output ]: + 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] diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index 5debc253da8..ca38f2efb01 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -951,7 +951,7 @@ def reset(self, base, logger): """ # Clear current values out of psf, gal, and stamp if they are not safe to reuse. # This means they are either marked as safe or indexed by something other than obj_num. - for field in ['psf', 'gal', 'stamp']: + for field in ('psf', 'gal', 'stamp'): if field in base: galsim.config.RemoveCurrent(base[field], keep_safe=True, index_key='obj_num') diff --git a/galsim/config/value.py b/galsim/config/value.py index 1cc1a112bbc..31b45852ca4 100644 --- a/galsim/config/value.py +++ b/galsim/config/value.py @@ -207,7 +207,7 @@ def EvaluateCurrentValue(key, config, base, value_type=None): that the value is the right type.] """ if not isinstance(config[key], dict): - if value_type is not None or (isinstance(config[key],str) and config[key][0] in ['@','$']): + if value_type is not None or (isinstance(config[key],str) and config[key][0] in ('@','$')): # This will work fine to evaluate the current value, but will also # compute it if necessary #print('Not dict. Parse value normally') @@ -383,9 +383,9 @@ def _GetBoolValue(param): """ @brief Convert a string to a bool """ if isinstance(param,str): - if param.strip().upper() in [ 'TRUE', 'YES' ]: + if param.strip().upper() in ('TRUE', 'YES'): return True - elif param.strip().upper() in [ 'FALSE', 'NO' ]: + elif param.strip().upper() in ('FALSE', 'NO'): return False else: try: diff --git a/galsim/config/wcs.py b/galsim/config/wcs.py index 6785c992a18..cc8706ead74 100644 --- a/galsim/config/wcs.py +++ b/galsim/config/wcs.py @@ -64,7 +64,7 @@ def BuildWCS(config, key, base, logger=None): wcs_type = 'PixelScale' # For these two, just do the usual ParseValue function. - if wcs_type in ['Eval', 'Current']: + if wcs_type in ('Eval', 'Current'): return galsim.config.ParseValue(config, key, base, None)[0] # Check if we can use the current cached object diff --git a/galsim/des/des_meds.py b/galsim/des/des_meds.py index c94e92cf720..77b43994f4a 100644 --- a/galsim/des/des_meds.py +++ b/galsim/des/des_meds.py @@ -93,8 +93,8 @@ def __init__(self, images, weight=None, badpix=None, seg=None, psf=None, raise galsim.GalSimValueError('Images must all be the same shape', images) # The others are optional, but if given, make sure they are ok. - for lst, name, isim in [ (weight, 'weight', True), (badpix, 'badpix', True), - (seg, 'seg', True), (psf, 'psf', False), (wcs, 'wcs', False) ]: + for lst, name, isim in ( (weight, 'weight', True), (badpix, 'badpix', True), + (seg, 'seg', True), (psf, 'psf', False), (wcs, 'wcs', False) ): if lst is not None: if not isinstance(lst,list): raise TypeError('%s should be a list'%name) diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index cdc43a1ad92..4a195f41476 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -477,7 +477,7 @@ def _writeHeader(self, header, bounds): warnings.simplefilter("ignore") fc = starlink.Ast.FitsChan(None, starlink.Atl.PyFITSAdapter(hdu) , "Encoding=FITS-WCS") # Let Ast know how big the image is that we'll be writing. - for key in ['NAXIS', 'NAXIS1', 'NAXIS2']: + for key in ('NAXIS', 'NAXIS1', 'NAXIS2'): if key in header: # pragma: no branch fc[key] = header[key] success = fc.write(self.wcsinfo) @@ -836,7 +836,7 @@ def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression= self.pv = _data[4] self.ab = _data[5] self.abp = _data[6] - if self.wcs_type in [ 'TAN', 'TPV' ]: + if self.wcs_type in ('TAN', 'TPV'): self.projection = 'gnomonic' elif self.wcs_type == 'STG': self.projection = 'stereographic' diff --git a/galsim/gsobject.py b/galsim/gsobject.py index 07be2484a8c..c71edf88f90 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -1603,7 +1603,7 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N prof *= flux_scale # If necessary, convolve by the pixel - if method in ['auto', 'fft', 'real_space']: + if method in ('auto', 'fft', 'real_space'): if method == 'auto': real_space = None elif method == 'fft': @@ -1635,13 +1635,13 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N else: # If not using phot, but doing sensor, then make a copy. if sensor is not None: - if imview.dtype in [np.float32, np.float64]: + if imview.dtype in (np.float32, np.float64): dtype = None else: dtype = np.float64 draw_image = imview.real.subsample(n_subsample, n_subsample, dtype=dtype) draw_image.setCenter(0,0) - if method in ['auto', 'fft', 'real_space']: + if method in ('auto', 'fft', 'real_space'): # Need to reconvolve by the new smaller pixel instead prof = Convolve( prof_no_pixel, @@ -1671,7 +1671,7 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N photons = PhotonArray.makeFromImage(draw_image, rng=ud) for op in surface_ops: op.applyTo(photons, local_wcs) - if imview.dtype in [np.float32, np.float64]: + if imview.dtype in (np.float32, np.float64): added_photons = sensor.accumulate(photons, imview, orig_center) else: # Need a temporary @@ -1713,12 +1713,12 @@ def drawReal(self, image, add_to_image=False): if image.wcs is None or not image.wcs.isPixelScale(): raise GalSimValueError("drawReal requires an image with a PixelScale wcs", image) - if image.dtype in [ np.float64, np.float32 ] and not add_to_image and image.iscontiguous: + if image.dtype in (np.float64, np.float32) and not add_to_image and image.iscontiguous: self._drawReal(image) return image.array.sum(dtype=float) else: # Need a temporary - if image.dtype in [ np.complex128, np.int32, np.uint32 ]: + if image.dtype in (np.complex128, np.int32, np.uint32): im1 = ImageD(bounds=image.bounds, scale=image.scale) else: im1 = ImageF(bounds=image.bounds, scale=image.scale) @@ -1803,7 +1803,7 @@ def drawFFT_makeKImage(self, image): raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk) bounds = _BoundsI(0,Nk//2,-Nk//2,Nk//2) - if image.dtype in [ np.complex128, np.float64, np.int32, np.uint32 ]: + if image.dtype in (np.complex128, np.float64, np.int32, np.uint32): kimage = ImageCD(bounds=bounds, scale=dk) else: kimage = ImageCF(bounds=bounds, scale=dk) @@ -2136,7 +2136,7 @@ def drawPhot(self, image, gain=1., add_to_image=False, for op in surface_ops: op.applyTo(photons, local_wcs) - if image.dtype in [np.float32, np.float64]: + if image.dtype in (np.float32, np.float64): added_flux += sensor.accumulate(photons, image, orig_center, resume=resume) resume = True # Resume from this point if there are any further iterations. else: diff --git a/galsim/image.py b/galsim/image.py index 82e45a99ac5..d66f6e44d2e 100644 --- a/galsim/image.py +++ b/galsim/image.py @@ -465,7 +465,7 @@ def isconst(self): return self._array.flags.writeable == False @property def iscomplex(self): return self._array.dtype.kind == 'c' @property - def isinteger(self): return self._array.dtype.kind in ['i','u'] + def isinteger(self): return self._array.dtype.kind in ('i','u') @property def iscontiguous(self): @@ -1415,7 +1415,7 @@ def calculateMomentRadius(self, center=None, flux=None, rtype='det'): @returns an estimate of the radius in physical units defined by the pixel scale (or both estimates if rtype == 'both'). """ - if rtype not in ['trace', 'det', 'both']: + if rtype not in ('trace', 'det', 'both'): raise GalSimValueError("Invalid rtype.", rtype, ('trace', 'det', 'both')) if center is None: @@ -1429,7 +1429,7 @@ def calculateMomentRadius(self, center=None, flux=None, rtype='det'): x = x - center.x + self.bounds.xmin y = y - center.y + self.bounds.ymin - if rtype in ['trace', 'both']: + if rtype in ('trace', 'both'): # Calculate trace measure: rsq = x*x + y*y Irr = np.sum(rsq * self.array, dtype=float) / flux @@ -1437,7 +1437,7 @@ def calculateMomentRadius(self, center=None, flux=None, rtype='det'): # This has all been done in pixels. So normalize according to the pixel scale. sigma_trace = (Irr/2.)**0.5 * self.scale - if rtype in ['det', 'both']: + if rtype in ('det', 'both'): # Calculate det measure: Ixx = np.sum(x*x * self.array, dtype=float) / flux Iyy = np.sum(y*y * self.array, dtype=float) / flux diff --git a/galsim/interpolant.py b/galsim/interpolant.py index 8a2c59a6fcc..46f67d1e507 100644 --- a/galsim/interpolant.py +++ b/galsim/interpolant.py @@ -72,7 +72,7 @@ def from_name(name, tol=1.e-4, gsparams=None): return Quintic(tol, gsparams) if name.lower().startswith('lanczos'): conserve_dc = True - if name[-1].upper() in ['T', 'F']: + if name[-1].upper() in ('T', 'F'): conserve_dc = (name[-1].upper() == 'T') name = name[:-1] try: diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 0d9d99c0ea9..97b462ad7ca 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -524,7 +524,7 @@ def _getFlux(self, flux, normalization): # need to rescale flux by the pixel area to get proper normalization. if flux is None: flux = self._image_flux - if normalization.lower() in ['surface brightness','sb']: + if normalization.lower() in ('surface brightness','sb'): flux *= self._wcs.pixelArea() return flux diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index d1be4969bfd..7a0374f7c83 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -574,7 +574,7 @@ def __getstate__(self): # Let unpickled object reconstruct cached values on-the-fly instead of including them in the # pickle. d = self.__dict__.copy() - for k in ['_rho', '_u', '_v', '_rsqr']: + for k in ('_rho', '_u', '_v', '_rsqr'): d.pop(k, None) return d diff --git a/galsim/scene.py b/galsim/scene.py index b32ea38dbbf..5d4dcd94c33 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -230,7 +230,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, def _apply_exclusion(self, exclusion_level, min_hlr=0, max_hlr=0, min_flux=0, max_flux=0): from ._pyfits import pyfits mask = np.ones(len(self.orig_index), dtype=bool) - if exclusion_level in ['marginal', 'bad_stamp']: + if exclusion_level in ('marginal', 'bad_stamp'): # First, read in what we need to impose selection criteria, if the appropriate # exclusion_level was chosen. @@ -286,7 +286,7 @@ def _apply_exclusion(self, exclusion_level, min_hlr=0, max_hlr=0, min_flux=0, ma "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) mask &= self.real_cat.stamp_flux > 0 - if exclusion_level in ['bad_fits', 'marginal']: + if exclusion_level in ('bad_fits', 'marginal'): # This 'exclusion_level' involves eliminating failed parametric fits (bad fit status # flags). In this case we only get rid of those with failed bulge+disk AND failed # Sersic fits, so there is no viable parametric model for the galaxy. diff --git a/galsim/sed.py b/galsim/sed.py index 462d48b6862..9fa3a615f62 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -848,7 +848,7 @@ def calculateDCRMomentShifts(self, bandpass, **kwargs): # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction # Check that they're valid for kw in kwargs: - if kw not in ['temperature', 'pressure', 'H2O_pressure']: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): raise (TypeError("Got unexpected keyword in calculateDCRMomentShifts: {0}" .format(kw))) diff --git a/galsim/table.py b/galsim/table.py index bd8344392aa..6e32cbb291d 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -106,7 +106,7 @@ def __init__(self, x, f, interpolant=None, x_log=False, f_log=False): raise GalSimIncompatibleValuesError("Input array lengths don't match", x=x, f=f) if interpolant == 'spline' and len(x) < 3: raise GalSimValueError("Input arrays too small to spline interpolate", x) - if interpolant in ['linear', 'ceil', 'floor', 'nearest'] and len(x) < 2: + if interpolant in ('linear', 'ceil', 'floor', 'nearest') and len(x) < 2: raise GalSimValueError("Input arrays too small to interpolate", x) # turn x and f into numpy arrays so that all subsequent math is possible (unlike for From f0424c493ca25a44ca26a4b876126a1767a94b91 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Wed, 16 May 2018 23:53:07 -0400 Subject: [PATCH 82/96] Remove unused f1,f2 variables in rho (#755) --- galsim/phase_psf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index 7a0374f7c83..c3d71610f24 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -538,8 +538,6 @@ def rho(self): """ Unit-disk normalized pupil plane coordinate as a complex number: (x, y) => x + 1j * y. """ - f1 = np.fft.fftfreq(self.npix, 1./self.pupil_plane_size) - f2 = np.fft.fftfreq(self.npix, self.diam/self.pupil_plane_size/2.0) u = np.fft.fftshift(np.fft.fftfreq(self.npix, self.diam/self.pupil_plane_size/2.0)) u, v = np.meshgrid(u, u) return u + 1j * v From ecb8dd960dcab75501c4bb80dfc2a7b264b4420c Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 00:06:50 -0400 Subject: [PATCH 83/96] Add text of original error if COSMOS noise file can't be read. (#755) --- galsim/correlatednoise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 3da2d8c0aaa..555301d9692 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -1372,8 +1372,8 @@ def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_i raise OSError("The file %r does not exist."%(file_name)) try: cfimage = fits.read(file_name) - except (IOError, OSError, AttributeError, TypeError): - raise OSError("Unable to read COSMOSNoise file %s"%(file_name)) + except (IOError, OSError, AttributeError, TypeError) as e: + raise OSError("Unable to read COSMOSNoise file %s.\n%r"%(file_name,e)) # Then check for negative variance before doing anything time consuming if variance < 0: From 72856976f58cb7830892a6f96cec39b31102f99c Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 00:40:22 -0400 Subject: [PATCH 84/96] Change a couple GalSimConfigErrors into GalSimConfigValueError (#755) --- galsim/config/image_scattered.py | 5 +++-- galsim/config/noise.py | 4 +++- galsim/config/stamp.py | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/galsim/config/image_scattered.py b/galsim/config/image_scattered.py index 33c335e40de..6d46791a361 100644 --- a/galsim/config/image_scattered.py +++ b/galsim/config/image_scattered.py @@ -92,8 +92,9 @@ def buildImage(self, config, base, image_num, obj_num, logger): base['current_image'] = full_image if 'image_pos' in config and 'world_pos' in config: - raise galsim.GalSimConfigError( - "Both image_pos and world_pos specified for Scattered image.") + 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 diff --git a/galsim/config/noise.py b/galsim/config/noise.py index 136ae857374..a6025091813 100644 --- a/galsim/config/noise.py +++ b/galsim/config/noise.py @@ -153,7 +153,9 @@ def GetSky(config, base, logger=None): logger = galsim.config.LoggerWrapper(logger) if 'sky_level' in config: if 'sky_level_pixel' in config: - raise galsim.GalSimConfigError("Cannot specify both sky_level and sky_level_pixel") + raise galsim.GalSimConfigValueError( + "Cannot specify both sky_level and sky_level_pixel", + (config['sky_level'], config['sky_level_pixel'])) sky_level = galsim.config.ParseValue(config,'sky_level',base,float)[0] logger.debug('image %d, obj %d: sky_level = %f', base.get('image_num',0),base.get('obj_num',0), sky_level) diff --git a/galsim/config/stamp.py b/galsim/config/stamp.py index ca38f2efb01..c3a89cab66c 100644 --- a/galsim/config/stamp.py +++ b/galsim/config/stamp.py @@ -798,8 +798,7 @@ def whiten(self, prof, image, config, base, logger): 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') + 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") From b33043ede05b4306ce4b1d86fa957b6889892d35 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 09:25:05 -0400 Subject: [PATCH 85/96] typos (#755) --- galsim/_pyfits.py | 2 +- galsim/bounds.py | 2 +- galsim/config/image_scattered.py | 2 +- galsim/integ.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/galsim/_pyfits.py b/galsim/_pyfits.py index c67e8921bd5..45750c24c42 100644 --- a/galsim/_pyfits.py +++ b/galsim/_pyfits.py @@ -17,7 +17,7 @@ # # We used to support legacy pyfits in addition to astropy.io.fits. We still call -# astropy.io.fits in the code, but we have removed the legacy compatibility hacks. +# astropy.io.fits pyfits in the code, but we have removed the legacy compatibility hacks. import astropy.io.fits as pyfits diff --git a/galsim/bounds.py b/galsim/bounds.py index a765d761e54..ff06d21c015 100644 --- a/galsim/bounds.py +++ b/galsim/bounds.py @@ -429,7 +429,7 @@ def _check_scalar(self, x, name): if x == int(x): return except (TypeError, ValueError): pass - raise TypeError("%s must be a integer value"%name) + raise TypeError("%s must be an integer value"%name) def numpyShape(self): "A simple utility function to get the numpy shape that corresponds to this Bounds object." diff --git a/galsim/config/image_scattered.py b/galsim/config/image_scattered.py index 6d46791a361..9d42d75d133 100644 --- a/galsim/config/image_scattered.py +++ b/galsim/config/image_scattered.py @@ -58,7 +58,7 @@ def setup(self, config, base, image_num, obj_num, ignore, logger): if (full_xsize <= 0) or (full_ysize <= 0): raise galsim.GalSimConfigError( - "Both image.stamp_xsize and image.stamp_ysize need to be defined and > 0.") + "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 diff --git a/galsim/integ.py b/galsim/integ.py index e25ed34f65d..1dc4b6842ff 100644 --- a/galsim/integ.py +++ b/galsim/integ.py @@ -147,7 +147,7 @@ def __init__(self): # subclasses must define # 1) a method `.calculateWaves(bandpass)` which will return the wavelengths at which to # evaluate the integrand - # 2) an function attribute `.rule` which takes a integrand function as its first + # 2) an function attribute `.rule` which takes an integrand function as its first # argument, and a list of evaluation wavelengths as its second argument, and returns # an approximation to the integral. (E.g., the function midptRule above) From 2b79c6959bef0b253afa69c19e19777b94bc293a Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 09:54:13 -0400 Subject: [PATCH 86/96] Add galsim_warn helper function to simplify code that raises a GalSimWarning (#755) --- galsim/chromatic.py | 15 +++---- galsim/config/input_nfw.py | 1 - galsim/convolve.py | 73 ++++++++++----------------------- galsim/correlatednoise.py | 12 +++--- galsim/detectors.py | 12 ++---- galsim/errors.py | 7 ++++ galsim/fits.py | 12 +++--- galsim/fitswcs.py | 11 +++-- galsim/fouriersqrt.py | 5 +-- galsim/gsobject.py | 27 +++++------- galsim/interpolatedimage.py | 8 ++-- galsim/lensing_ps.py | 24 ++++------- galsim/main.py | 2 +- galsim/phase_psf.py | 35 +++++++--------- galsim/phase_screens.py | 21 +++------- galsim/scene.py | 26 ++++-------- galsim/utilities.py | 14 +++---- galsim/vonkarman.py | 9 ++-- tests/test_config_image.py | 1 + tests/test_convolve.py | 4 -- tests/test_image.py | 2 - tests/test_interpolatedimage.py | 2 - tests/test_lensing.py | 4 +- 23 files changed, 119 insertions(+), 208 deletions(-) diff --git a/galsim/chromatic.py b/galsim/chromatic.py index d080ddd56be..c716c0736f2 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -37,7 +37,7 @@ from . import utilities from . import integ from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimValueError -from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError, GalSimWarning +from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError, galsim_warn class ChromaticObject(object): """Base class for defining wavelength-dependent objects. @@ -1467,10 +1467,9 @@ def detjac(w): self.SED *= detjac if obj.interpolated and self.chromatic: - import warnings - warnings.warn("Cannot render image with chromatic transformation applied to it " - "using interpolation between stored images. Reverting to " - "non-interpolated version.", GalSimWarning) + galsim_warn("Cannot render image with chromatic transformation applied to it " + "using interpolation between stored images. Reverting to " + "non-interpolated version.") obj = obj.deinterpolated self.interpolated = obj.interpolated @@ -1944,11 +1943,9 @@ def __init__(self, *args, **kwargs): if not obj.separable and not isinstance(obj, ChromaticSum): n_nonsep += 1 if obj.interpolated: n_interp += 1 if n_nonsep>1 and n_interp>0: - import warnings - warnings.warn( + galsim_warn( "Image rendering for this convolution cannot take advantage of " - "interpolation-related optimization. Will use full profile evaluation.", - GalSimWarning) + "interpolation-related optimization. Will use full profile evaluation.") # Assemble wave_lists self.wave_list, _, _ = utilities.combine_wave_list(self.obj_list) diff --git a/galsim/config/input_nfw.py b/galsim/config/input_nfw.py index 6bcec94f087..5846514ef09 100644 --- a/galsim/config/input_nfw.py +++ b/galsim/config/input_nfw.py @@ -19,7 +19,6 @@ from __future__ import print_function import galsim -import warnings # This file adds input type nfw_halo and value types NFWHaloShear and NFWHaloMagnification. diff --git a/galsim/convolve.py b/galsim/convolve.py index 1d04fa09c7b..848f8ff96d8 100644 --- a/galsim/convolve.py +++ b/galsim/convolve.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject, ChromaticConvolution from .utilities import lazy_property, doc_inherit -from .errors import GalSimWarning, GalSimError, convert_cpp_errors +from .errors import GalSimError, convert_cpp_errors, galsim_warn def Convolve(*args, **kwargs): """A function for convolving 2 or more GSObject or ChromaticObject instances. @@ -161,37 +161,26 @@ def __init__(self, *args, **kwargs): # Warn if doing DFT convolution for objects with hard edges if not real_space and hard_edge: - import warnings if len(args) == 2: - msg = """ - Doing convolution of 2 objects, both with hard edges. - This might be more accurate and/or faster using real_space=True""" + galsim_warn("Doing convolution of 2 objects, both with hard edges. " + "This might be more accurate and/or faster using real_space=True") else: - msg = """ - Doing convolution where all objects have hard edges. - There might be some inaccuracies due to ringing in k-space.""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Doing convolution where all objects have hard edges. " + "There might be some inaccuracies due to ringing in k-space.") if real_space: # Can't do real space if nobj > 2 if len(args) > 2: - import warnings - msg = """ - Real-space convolution of more than 2 objects is not implemented. - Switching to DFT method.""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Real-space convolution of more than 2 objects is not implemented. " + "Switching to DFT method.") real_space = False # Also can't do real space if any object is not analytic, so check for that. else: for obj in args: if not obj.is_analytic_x: - import warnings - msg = """ - A component to be convolved is not analytic in real space. - Cannot use real space convolution. - Switching to DFT method.""" - warnings.warn(msg, GalSimWarning) + galsim_warn("A component to be convolved is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") real_space = False break @@ -221,9 +210,8 @@ def _noise(self): for i, obj in enumerate(self.obj_list): if obj.noise is not None: if _noise is not None: - import warnings - warnings.warn("Unable to propagate noise in galsim.Convolution when " - "multiple objects have noise attribute", GalSimWarning) + galsim_warn("Unable to propagate noise in galsim.Convolution when " + "multiple objects have noise attribute") break _noise = obj.noise others = [ obj2 for k, obj2 in enumerate(self.obj_list) if k != i ] @@ -482,8 +470,7 @@ def orig_obj(self): return self._orig_obj @property def _noise(self): if self.orig_obj.noise is not None: - import warnings - warnings.warn("Unable to propagate noise in galsim.Deconvolution", GalSimWarning) + galsim_warn("Unable to propagate noise in galsim.Deconvolution") return None def __eq__(self, other): @@ -644,20 +631,13 @@ def __init__(self, obj, real_space=None, gsparams=None): # Warn if doing DFT convolution for objects with hard edges. if not real_space and hard_edge: - import warnings - msg = """ - Doing auto-convolution of object with hard edges. - This might be more accurate and/or faster using real_space=True""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Doing auto-convolution of object with hard edges. " + "This might be more accurate and/or faster using real_space=True") # Can't do real space if object is not analytic, so check for that. if real_space and not obj.is_analytic_x: - import warnings - msg = """ - Object to be auto-convolved is not analytic in real space. - Cannot use real space convolution. - Switching to DFT method.""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Object to be auto-convolved is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") real_space = False # Save the construction parameters (as they are at this point) as attributes so they @@ -682,8 +662,7 @@ def real_space(self): return self._real_space @property def _noise(self): if self.orig_obj.noise is not None: - import warnings - warnings.warn("Unable to propagate noise in galsim.AutoConvolution", GalSimWarning) + galsim_warn("Unable to propagate noise in galsim.AutoConvolution") return None def __eq__(self, other): @@ -788,20 +767,13 @@ def __init__(self, obj, real_space=None, gsparams=None): # Warn if doing DFT convolution for objects with hard edges. if not real_space and hard_edge: - import warnings - msg = """ - Doing auto-correlation of object with hard edges. - This might be more accurate and/or faster using real_space=True""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Doing auto-correlation of object with hard edges. " + "This might be more accurate and/or faster using real_space=True") # Can't do real space if object is not analytic, so check for that. if real_space and not obj.is_analytic_x: - import warnings - msg = """ - Object to be auto-correlated is not analytic in real space. - Cannot use real space convolution. - Switching to DFT method.""" - warnings.warn(msg, GalSimWarning) + galsim_warn("Object to be auto-correlated is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") real_space = False # Save the construction parameters (as they are at this point) as attributes so they @@ -826,8 +798,7 @@ def real_space(self): return self._real_space @property def _noise(self): if self.orig_obj.noise is not None: - import warnings - warnings.warn("Unable to propagate noise in galsim.AutoCorrelation", GalSimWarning) + galsim_warn("Unable to propagate noise in galsim.AutoCorrelation") return None def __eq__(self, other): diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index 555301d9692..d305a316155 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -28,7 +28,7 @@ from .gsparams import GSParams from . import utilities from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimUndefinedBoundsError -from .errors import GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimIncompatibleValuesError, galsim_warn def whitenNoise(self, noise): # This will be inserted into the Image class as a method. So self = image. @@ -113,17 +113,15 @@ def rng(self): return self._rng def __add__(self, other): from . import wcs if not wcs.compatible(self.wcs, other.wcs): - import warnings - warnings.warn("Adding two CorrelatedNoise objects with incompatible WCS. " - "The result will have the WCS of the first object.", GalSimWarning) + galsim_warn("Adding two CorrelatedNoise objects with incompatible WCS. " + "The result will have the WCS of the first object.") return _BaseCorrelatedNoise(self.rng, self._profile + other._profile, self.wcs) def __sub__(self, other): from . import wcs if not wcs.compatible(self.wcs, other.wcs): - import warnings - warnings.warn("Subtracting two CorrelatedNoise objects with incompatible WCS. " - "The result will have the WCS of the first object.", GalSimWarning) + galsim_warn("Subtracting two CorrelatedNoise objects with incompatible WCS. " + "The result will have the WCS of the first object.") return _BaseCorrelatedNoise(self.rng, self._profile - other._profile, self.wcs) def __mul__(self, variance_ratio): diff --git a/galsim/detectors.py b/galsim/detectors.py index a58143e937c..bbfbf9ed6ac 100644 --- a/galsim/detectors.py +++ b/galsim/detectors.py @@ -24,8 +24,7 @@ import sys from .image import Image -from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError, galsim_warn def applyNonlinearity(self, NLfunc, *args): """ @@ -146,9 +145,7 @@ def addReciprocityFailure(self, exp_time, alpha, base_flux): base_flux, 0, None) if np.any(self.array<0): - import warnings - warnings.warn("One or more pixel values are negative and will be set as 'nan'.", - GalSimWarning) + galsim_warn("One or more pixel values are negative and will be set as 'nan'.") p0 = exp_time*base_flux a = alpha/np.log(10) @@ -219,9 +216,8 @@ def applyIPC(self, IPC_kernel, edge_treatment='extend', fill_value=None, kernel_ # Check and enforce correct normalization for the kernel if kernel_normalization and abs(ipc_kernel.sum()-1) > 10.*np.finfo(ipc_kernel.dtype.type).eps: - import warnings - warnings.warn("The entries in the IPC kernel did not sum to 1. Scaling the kernel to "\ - +"ensure correct normalization.", GalSimWarning) + galsim_warn("The entries in the IPC kernel did not sum to 1. Scaling the kernel to " + "ensure correct normalization.") IPC_kernel = IPC_kernel/ipc_kernel.sum() # edge_treatment can be 'extend', 'wrap' or 'crop' diff --git a/galsim/errors.py b/galsim/errors.py index f6707748339..2c85021290e 100644 --- a/galsim/errors.py +++ b/galsim/errors.py @@ -19,6 +19,7 @@ # Define the class hierarchy for errors and warnings emitted by GalSim that aren't # obviously one of the standard python errors. +import warnings from builtins import super from contextlib import contextmanager @@ -393,6 +394,7 @@ def __repr__(self): return 'galsim.GalSimNotImplementedError(%r)'%(str(self)) +# Note: Can use galsim_warn to raise warnings with this warning class. class GalSimWarning(UserWarning): """The base class for GalSim-emitted warnings. """ @@ -415,3 +417,8 @@ def convert_cpp_errors(error_type=GalSimError): yield except RuntimeError as err: raise error_type(str(err)) + +def galsim_warn(message): + """A helper function for emitting a GalSimWarning with the given message + """ + warnings.warn(message, GalSimWarning) diff --git a/galsim/fits.py b/galsim/fits.py index 2e01336fd0c..16c3994b954 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -28,7 +28,7 @@ import numpy as np from .image import Image -from .errors import GalSimError, GalSimValueError, GalSimWarning, GalSimIncompatibleValuesError +from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, galsim_warn ############################################################################################## @@ -733,9 +733,8 @@ def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto'): if dt in Image.valid_dtypes: data = hdu.data else: - import warnings - warnings.warn("No C++ Image template instantiation for data type %s. " - "Using numpy.float64 instead."%(dt), GalSimWarning) + galsim_warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt)) data = hdu.data.astype(np.float64) image = Image(array=data) @@ -888,9 +887,8 @@ def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='aut if dt in Image.valid_dtypes: data = hdu.data else: - import warnings - warnings.warn("No C++ Image template instantiation for data type %s. " - "Using numpy.float64 instead."%(dt), GalSimWarning) + galsim_warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt)) data = hdu.data.astype(np.float64) nimages = data.shape[0] diff --git a/galsim/fitswcs.py b/galsim/fitswcs.py index 4a195f41476..81f018e6942 100644 --- a/galsim/fitswcs.py +++ b/galsim/fitswcs.py @@ -30,7 +30,7 @@ from . import _galsim from . import fits from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimNotImplementedError, GalSimWarning, convert_cpp_errors +from .errors import GalSimNotImplementedError, convert_cpp_errors, galsim_warn ######################################################################################### # @@ -712,8 +712,8 @@ def _xy(self, ra, dec, color=None): if len(vals) < 6: raise GalSimError('wcstools sky2xy returned invalid result for %f,%f'%(ra,dec)) if len(vals) > 6: - warnings.warn('wcstools sky2xy indicates that %f,%f is off the image. ' - 'output is %r'%(ra,dec,results), GalSimWarning) + galsim_warn("wcstools sky2xy indicates that %f,%f is off the image. " + "output is %r"%(ra,dec,results)) x = float(vals[4]) y = float(vals[5]) @@ -1650,9 +1650,8 @@ def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', # Finally, this one is really the last resort, since it only reads in the linear part of the # WCS. It defaults to the equivalent of a pixel scale of 1.0 if even these are not present. if not suppress_warning: - warnings.warn("All the fits WCS types failed to read %r. Using AffineTransform " - "instead, which will not really be correct."%(file_name), - GalSimWarning) + galsim_warn("All the fits WCS types failed to read %r. Using AffineTransform " + "instead, which will not really be correct."%(file_name)) return AffineTransform._readHeader(header) # Let this function work like a class in config. diff --git a/galsim/fouriersqrt.py b/galsim/fouriersqrt.py index 1be863eba38..f31265f4778 100644 --- a/galsim/fouriersqrt.py +++ b/galsim/fouriersqrt.py @@ -23,7 +23,7 @@ from .gsobject import GSObject from .chromatic import ChromaticObject from .utilities import lazy_property, doc_inherit -from .errors import GalSimWarning, convert_cpp_errors +from .errors import convert_cpp_errors, galsim_warn def FourierSqrt(obj, gsparams=None): @@ -107,8 +107,7 @@ def _sbp(self): @property def _noise(self): if self.orig_obj.noise is not None: - import warnings - warnings.warn("Unable to propagate noise in galsim.FourierSqrtProfile", GalSimWarning) + galsim_warn("Unable to propagate noise in galsim.FourierSqrtProfile") return None def __eq__(self, other): diff --git a/galsim/gsobject.py b/galsim/gsobject.py index c71edf88f90..0d5fcfefb84 100644 --- a/galsim/gsobject.py +++ b/galsim/gsobject.py @@ -59,8 +59,7 @@ from .position import PositionD, PositionI from .utilities import lazy_property, parse_pos_args from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimFFTSizeError, GalSimNotImplementedError -from .errors import GalSimWarning, convert_cpp_errors +from .errors import GalSimFFTSizeError, GalSimNotImplementedError, convert_cpp_errors, galsim_warn class GSObject(object): @@ -1522,16 +1521,14 @@ def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=N # Check that the user isn't convolving by a Pixel already. This is almost always an error. if method == 'auto' and isinstance(self, Convolution): if any([ isinstance(obj, Pixel) for obj in self.obj_list ]): - import warnings - warnings.warn( + galsim_warn( "You called drawImage with `method='auto'` " "for an object that includes convolution by a Pixel. " "This is probably an error. Normally, you should let GalSim " "handle the Pixel convolution for you. If you want to handle the Pixel " "convolution yourself, you can use method=no_pixel. Or if you really meant " "for your profile to include the Pixel and also have GalSim convolve by " - "an _additional_ Pixel, you can suppress this warning by using method=fft.", - GalSimWarning) + "an _additional_ Pixel, you can suppress this warning by using method=fft.") # Some parameters are only relevant for method == 'phot' if method != 'phot' and sensor is None: @@ -1987,13 +1984,11 @@ def _calculate_nphotons(self, n_photons, poisson_flux, max_extra_noise, rng): iN = int(n_photons + 0.5) if iN <= 0: - import warnings - warnings.warn("Automatic n_photons calculation did not end up with positive N. " - "(n_photons = {0}) No photons will be shot. " - " prof = {1}\n flux = {2}\n poisson_flux = {3}\n" - " max_extra_noise = {4}\n g = {5}".format( - n_photons, self, self.flux, poisson_flux, max_extra_noise, g), - GalSimWarning) + galsim_warn("Automatic n_photons calculation did not end up with positive N. " + "(n_photons = {0}) No photons will be shot.\n" + " prof = {1}\n flux = {2}\n poisson_flux = {3}\n" + " max_extra_noise = {4}\n g = {5}".format( + n_photons, self, self.flux, poisson_flux, max_extra_noise, g)) return 0, 1. return iN, g @@ -2081,11 +2076,9 @@ def drawPhot(self, image, gain=1., add_to_image=False, # Check that either n_photons is set to something or flux is set to something if n_photons == 0. and self.flux == 1.: - import warnings - warnings.warn( + galsim_warn( "Warning: drawImage for object with flux == 1, area == 1, and " - "exptime == 1, but n_photons == 0. This will only shoot a single photon.", - GalSimWarning) + "exptime == 1, but n_photons == 0. This will only shoot a single photon.") ud = UniformDeviate(rng) diff --git a/galsim/interpolatedimage.py b/galsim/interpolatedimage.py index 97b462ad7ca..92036a28538 100644 --- a/galsim/interpolatedimage.py +++ b/galsim/interpolatedimage.py @@ -35,7 +35,7 @@ from . import _galsim from . import fits from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError -from .errors import GalSimIncompatibleValuesError, GalSimWarning, convert_cpp_errors +from .errors import GalSimIncompatibleValuesError, convert_cpp_errors, galsim_warn class InterpolatedImage(GSObject): """A class describing non-parametric profiles specified using an Image, which can be @@ -909,10 +909,8 @@ def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, self._kimage.scale = 1. self._stepk = self._kimage.scale elif stepk < kimage.scale: - import warnings - warnings.warn( - "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.", - GalSimWarning) + galsim_warn( + "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.") self._stepk = kimage.scale else: self._stepk = stepk diff --git a/galsim/lensing_ps.py b/galsim/lensing_ps.py index ac52855504e..1d1082f7db5 100644 --- a/galsim/lensing_ps.py +++ b/galsim/lensing_ps.py @@ -32,7 +32,7 @@ from . import utilities from . import integ from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimNotImplementedError, GalSimWarning +from .errors import GalSimNotImplementedError, galsim_warn def theoryToObserved(gamma1, gamma2, kappa): """Helper function to convert theoretical lensing quantities to observed ones. @@ -972,11 +972,9 @@ def _getSingleShear(self, x, y, ii_g1, ii_g2, periodic): if not periodic: # We're not treating this as a periodic box, so issue a warning and set the # shear to zero for positions that are outside the original grid. - import warnings - warnings.warn( + galsim_warn( "Warning: position (%f,%f) not within the bounds (%s) of the gridded shear " - "values. Returning a shear of (0,0) for this point."%(x,y,self.bounds), - GalSimWarning) + "values. Returning a shear of (0,0) for this point."%(x,y,self.bounds)) return 0., 0. else: # Treat this as a periodic box. @@ -1070,11 +1068,10 @@ def _getSingleConvergence(self, x, y, ii_kappa, periodic): # Check that the position is in the bounds of the interpolated image if not self.bounds.includes(pos): if not periodic: - import warnings - warnings.warn( + galsim_warn( "Warning: position (%f,%f) not within the bounds (%s) of the gridded " "convergence values. Returning a convergence of 0 for this point."%( - x,y,self.bounds), GalSimWarning) + x,y,self.bounds)) return 0. else: # Treat this as a periodic box. @@ -1169,11 +1166,10 @@ def _getSingleMagnification(self, x, y, ii_mu, periodic): # Check that the position is in the bounds of the interpolated image if not self.bounds.includes(pos): if not periodic: - import warnings - warnings.warn( + galsim_warn( "Warning: position (%f,%f) not within the bounds (%s) of the gridded " "convergence values. Returning a magnification of 1 for this point."%( - x,y,self.bounds), GalSimWarning) + x,y,self.bounds)) return 1. else: # Treat this as a periodic box. @@ -1277,11 +1273,9 @@ def _getSingleLensing(self, x, y, ii_g1, ii_g2, ii_mu, periodic): # Check that the position is in the bounds of the interpolated image if not self.bounds.includes(pos): if not periodic: - import warnings - warnings.warn( + galsim_warn( "Warning: position (%f,%f) not within the bounds (%s) of the gridded " - "values. Returning 0 for lensing observables at this point."%(x,y,self.bounds), - GalSimWarning) + "values. Returning 0 for lensing observables at this point."%(x,y,self.bounds)) return 0., 0., 1. else: # Treat this as a periodic box. diff --git a/galsim/main.py b/galsim/main.py index c0a8aef5029..555321421df 100644 --- a/galsim/main.py +++ b/galsim/main.py @@ -26,7 +26,7 @@ import logging import pprint -from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimWarning +from .errors import GalSimError, GalSimValueError, GalSimRangeError def parse_args(): """Handle the command line arguments using either argparse (if available) or optparse. diff --git a/galsim/phase_psf.py b/galsim/phase_psf.py index c3d71610f24..1a1983b392e 100644 --- a/galsim/phase_psf.py +++ b/galsim/phase_psf.py @@ -80,7 +80,7 @@ from .interpolatedimage import InterpolatedImage from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError -from .errors import GalSimFFTSizeError, GalSimWarning +from .errors import GalSimFFTSizeError, galsim_warn class Aperture(object): """ Class representing a telescope aperture embedded in a larger pupil plane array -- for use @@ -290,24 +290,21 @@ def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, if pupil_plane_scale is not None: # Check input scale and warn if looks suspicious. if pupil_plane_scale > good_pupil_scale: - import warnings ratio = good_pupil_scale / pupil_plane_scale - warnings.warn("Input pupil_plane_scale may be too large for good sampling.\n" - "Consider decreasing pupil_plane_scale by a factor %f, and/or " - "check PhaseScreenPSF outputs for signs of folding in real " - "space."%(1./ratio), GalSimWarning) + galsim_warn("Input pupil_plane_scale may be too large for good sampling.\n" + "Consider decreasing pupil_plane_scale by a factor %f, and/or " + "check PhaseScreenPSF outputs for signs of folding in real " + "space."%(1./ratio)) else: pupil_plane_scale = good_pupil_scale if pupil_plane_size is not None: # Check input size and warn if looks suspicious if pupil_plane_size < good_pupil_size: - import warnings ratio = good_pupil_size / pupil_plane_size - warnings.warn("Input pupil_plane_size may be too small for good focal-plane" - "sampling.\n" - "Consider increasing pupil_plane_size by a factor %f, and/or " - "check PhaseScreenPSF outputs for signs of undersampling."%ratio, - GalSimWarning) + galsim_warn("Input pupil_plane_size may be too small for good focal-plane" + "sampling.\n" + "Consider increasing pupil_plane_size by a factor %f, and/or " + "check PhaseScreenPSF outputs for signs of undersampling."%ratio) else: pupil_plane_size = good_pupil_size self._generate_pupil_plane(circular_pupil, @@ -429,12 +426,10 @@ def _load_pupil_plane(self, pupil_plane_im, pupil_angle, pupil_plane_scale, good # Check sampling interval and warn if it's not good enough. if self.pupil_plane_scale > good_pupil_scale: - import warnings ratio = self.pupil_plane_scale / good_pupil_scale - warnings.warn("Input pupil plane image may not be sampled well enough!\n" - "Consider increasing sampling by a factor %f, and/or check " - "PhaseScreenPSF outputs for signs of folding in real space."%ratio, - GalSimWarning) + galsim_warn("Input pupil plane image may not be sampled well enough!\n" + "Consider increasing sampling by a factor %f, and/or check " + "PhaseScreenPSF outputs for signs of folding in real space."%ratio) if pupil_angle.rad == 0.: self._illuminated = pp_arr.astype(bool) @@ -1289,12 +1284,10 @@ def _finalize(self): observed_stepk = self._ii.stepk if observed_stepk < specified_stepk: - import warnings - warnings.warn( + galsim_warn( "The calculated stepk (%g) for PhaseScreenPSF is smaller than what was used " "to build the wavefront (%g). This could lead to aliasing problems. " - "Increasing pad_factor is recommended."%(observed_stepk, specified_stepk), - GalSimWarning) + "Increasing pad_factor is recommended."%(observed_stepk, specified_stepk)) @property def _sbp(self): diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 08747ebe941..819dde1c388 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -26,7 +26,7 @@ from . import utilities from . import fft from . import zernike -from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError, GalSimWarning +from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError, galsim_warn class AtmosphericScreen(object): @@ -215,18 +215,12 @@ def instantiate(self, kmin=0., kmax=np.inf, check=None): if check is not None and not self._suppress_warning: if check == 'FFT': if self.kmax != np.inf: - import warnings - warnings.warn( - "Instantiating AtmosphericScreen with kmax != inf " - "may yield surprising results when drawing using Fourier optics.", - GalSimWarning) + galsim_warn("Instantiating AtmosphericScreen with kmax != inf may yield " + "surprising results when drawing using Fourier optics.") if check == 'phot': if self.kmax == np.inf: - import warnings - warnings.warn( - "Instantiating AtmosphericScreen with kmax == inf " - "may yield surprising results when drawing using geometric optics.", - GalSimWarning) + galsim_warn("Instantiating AtmosphericScreen with kmax == inf may yield " + "surprising results when drawing using geometric optics.") # Note the magic number 0.00058 is actually ... wait for it ... @@ -666,10 +660,7 @@ def __init__(self, diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, raise GalSimValueError("Aberrations keyword must have length > 2", aberrations) # Check for non-zero value in first two places. Probably a mistake. if aberrations[0] != 0.0: - import warnings - warnings.warn( - "Detected non-zero value in aberrations[0] -- this value is ignored!", - GalSimWarning) + galsim_warn("Detected non-zero value in aberrations[0] -- this value is ignored!") aberrations = np.array(aberrations) self.aberrations = aberrations diff --git a/galsim/scene.py b/galsim/scene.py index 5d4dcd94c33..3406cd5fa2b 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -27,7 +27,7 @@ from .real import RealGalaxy, RealGalaxyCatalog from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError -from .errors import GalSimNotImplementedError, GalSimWarning +from .errors import GalSimNotImplementedError, galsim_warn # Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that # GalSim needs to make a GSObject representing that fit. It is simply the pixel scale, in arcsec, @@ -459,9 +459,7 @@ def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size index = self.selectRandomIndex(n_random, rng=rng) else: if n_random is not None: - import warnings - warnings.warn("Ignoring input n_random, since indices were specified!", - GalSimWarning) + galsim_warn("Ignoring input n_random, since indices were specified!") if hasattr(index, '__iter__'): indices = index @@ -505,15 +503,11 @@ def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size size_factor = 0.6 gal_list = [ gal.dilate(size_factor) * flux_factor for gal in gal_list ] elif self.getUseSample() == '25.2': - import warnings - warnings.warn( - 'Ignoring `deep` argument, because the sample being used already ' - 'corresponds to a flux limit of F814W<25.2', GalSimWarning) + galsim_warn("Ignoring `deep` argument, because the sample being used already " + "corresponds to a flux limit of F814W<25.2") else: - import warnings - warnings.warn( - 'Ignoring `deep` argument, because the sample being used does not ' - 'corresponds to a flux limit of F814W<23.5', GalSimWarning) + galsim_warn("Ignoring `deep` argument, because the sample being used does not " + "corresponds to a flux limit of F814W<23.5") # Store the orig_index as gal.index regardless of whether we have a RealGalaxy or not. # It gets set as part of making a real galaxy, but not by _buildParametric. @@ -550,11 +544,9 @@ def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False): if hasattr(self.real_cat, 'weight'): use_weights = self.real_cat.weight[self.orig_index] else: - import warnings - warnings.warn('Selecting random object without correcting for catalog-level ' - 'selection effects. This correction requires the existence of ' - 'real catalog with valid weights in addition to parametric one.', - GalSimWarning) + galsim_warn("Selecting random object without correcting for catalog-level " + "selection effects. This correction requires the existence of " + "real catalog with valid weights in addition to parametric one.") use_weights = None # By default, get the number of RNG calls. We then decide whether or not to return them diff --git a/galsim/utilities.py b/galsim/utilities.py index 1ebd1428eac..efc2487d626 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -27,7 +27,7 @@ import numpy as np from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimRangeError -from .errors import GalSimWarning +from .errors import galsim_warn def roll2d(image, shape): @@ -719,8 +719,7 @@ def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): img.setOrigin(image.origin) elif suppress_warnings is False: - import warnings - warnings.warn("Individual images could not be assigned a WCS automatically.", GalSimWarning) + galsim_warn("Individual images could not be assigned a WCS automatically.") return im_list, offsets @@ -895,18 +894,15 @@ def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False img.wcs = img_wcs elif suppress_warnings is False: - import warnings - warnings.warn("Interleaved image could not be assigned a WCS automatically.", GalSimWarning) + galsim_warn("Interleaved image could not be assigned a WCS automatically.") # Assign a possibly non-trivial origin and warn if individual image have different origins. orig = im_list[0].origin img.setOrigin(orig) for im in im_list[1:]: if not im.origin==orig: # pragma: no cover - import warnings - warnings.warn("Images in `im_list' have multiple values for origin. Assigning the " - "origin of the first Image instance in 'im_list' to the interleaved " - "image.", GalSimWarning) + galsim_warn("Images in `im_list' have multiple values for origin. Assigning the " + "origin of the first Image instance in 'im_list' to the interleaved image.") break return img diff --git a/galsim/vonkarman.py b/galsim/vonkarman.py index 66d72c1c736..3f11a7d011e 100644 --- a/galsim/vonkarman.py +++ b/galsim/vonkarman.py @@ -27,7 +27,7 @@ from .utilities import lazy_property, doc_inherit from .position import PositionD from .angle import arcsec, AngleUnit -from .errors import GalSimError, GalSimWarning, convert_cpp_errors +from .errors import GalSimError, convert_cpp_errors, galsim_warn class VonKarman(GSObject): @@ -124,10 +124,9 @@ def _sbvk(self): self._delta = sbvk.getDelta() if not self._suppress: if self._delta > self._gsparams.maxk_threshold: - import warnings - warnings.warn("VonKarman delta-function component is larger than maxk_threshold. " - "Please see docstring for information about this component and how " - "to toggle it.", GalSimWarning) + galsim_warn("VonKarman delta-function component is larger than maxk_threshold. " + "Please see docstring for information about this component and how " + "to toggle it.") if self._do_delta: with convert_cpp_errors(): sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, diff --git a/tests/test_config_image.py b/tests/test_config_image.py index f319e4b5101..e339bc8efbc 100644 --- a/tests/test_config_image.py +++ b/tests/test_config_image.py @@ -1822,6 +1822,7 @@ def test_multirng(): v = rngb() * 50. - 25. world_pos = galsim.PositionD(u,v) with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") psf_g1, psf_g2 = psf_ps.getShear(world_pos) if len(w) > 0: assert not psf_ps.bounds.includes(world_pos) diff --git a/tests/test_convolve.py b/tests/test_convolve.py index fc3fa2a4906..0ab050ea36c 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -92,8 +92,6 @@ def test_convolve(): # Test photon shooting. with assert_warns(galsim.GalSimWarning): do_shoot(conv,myImg,"Moffat * Pixel") - # Clear the warnings registry for later so we can test that appropriate warnings are raised. - galsim.Convolution.__init__.__globals__['__warningregistry__'].clear() # Convolution of just one argument should be equivalent to that argument. single = galsim.Convolve(psf) @@ -251,8 +249,6 @@ def test_shearconvolve(): # Test photon shooting. with assert_warns(galsim.GalSimWarning): do_shoot(conv,myImg,"sheared Gaussian * Pixel") - # Clear the warnings registry for later so we can test that appropriate warnings are raised. - galsim.GSObject.drawImage.__globals__['__warningregistry__'].clear() @timer diff --git a/tests/test_image.py b/tests/test_image.py index 1b22622f290..0084fa32e0f 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3250,8 +3250,6 @@ def test_FITS_bad_type(): """Test that reading FITS files with an invalid data type succeeds by converting the type to float64. """ - import warnings - # We check this by monkey patching the Image.valid_types list to not include int16 # and see if it reads properly and raises the appropriate warning. orig_dtypes = galsim.Image.valid_dtypes diff --git a/tests/test_interpolatedimage.py b/tests/test_interpolatedimage.py index fad67c5fefa..d0af7241dd7 100644 --- a/tests/test_interpolatedimage.py +++ b/tests/test_interpolatedimage.py @@ -1110,8 +1110,6 @@ def test_stepk_maxk(): def test_kroundtrip(): """ Test that GSObjects `a` and `b` are the same when b = InterpolatedKImage(a.drawKImage) """ - import warnings - a = final kim_a = a.drawKImage() b = galsim.InterpolatedKImage(kim_a) diff --git a/tests/test_lensing.py b/tests/test_lensing.py index 501c1b1c73a..05fad8a75f7 100644 --- a/tests/test_lensing.py +++ b/tests/test_lensing.py @@ -21,7 +21,6 @@ import math import os import sys -import warnings import galsim from galsim_test_helpers import * @@ -689,8 +688,7 @@ def test_shear_get(): (g1_r[0,0], g2_r[0,0], mu[0,0])) # Test outside of bounds - with warnings.catch_warnings(): - warnings.filterwarnings("ignore") + with assert_warns(galsim.GalSimWarning): np.testing.assert_almost_equal(my_ps.getShear((5000,5000)), (0,0)) np.testing.assert_almost_equal(my_ps.getShear((5000,5000), reduced=False), (0,0)) np.testing.assert_almost_equal(my_ps.getConvergence((5000,5000)), 0) From 9e57dddb688f4a1f7eb00d0e385cb04385621f6e Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 10:42:46 -0400 Subject: [PATCH 87/96] Simplify checks of valid PSFEx file, and add test that invalid file raises OSError (#755) --- galsim/des/des_psfex.py | 43 +++++++++++++----------------- tests/des_data/invalid_psfcat.psf | Bin 0 -> 23040 bytes tests/test_des.py | 8 ++++++ 3 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 tests/des_data/invalid_psfcat.psf diff --git a/galsim/des/des_psfex.py b/galsim/des/des_psfex.py index c87a3280d35..9014fad4762 100644 --- a/galsim/des/des_psfex.py +++ b/galsim/des/des_psfex.py @@ -199,37 +199,30 @@ def read(self): psf_samp = hdu.header['PSF_SAMP'] # The basis object is a data cube (assuming PSFNAXIS==3) - # Note: older pyfits versions don't get the shape right. - # For newer pyfits versions the reshape command should be a no op. - basis = hdu.data.field('PSF_MASK')[0].reshape(psf_axis3,psf_axis2,psf_axis1) + basis = hdu.data.field('PSF_MASK')[0] # Make sure to close the hdu before we might raise exceptions. if hdu_list: hdu_list.close() # Check for valid values of all these things. - if pol_naxis != 2: # pragma: no cover - raise OSError("PSFEx: Expected POLNAXIS == 2, got %d"%pol_naxis) - if not (pol_name1.startswith('X') and pol_name1.endswith('IMAGE')): # pragma: no cover - raise OSError("PSFEx: Expected POLNAME1 == X*_IMAGE, got %s"%pol_name1) - if not (pol_name2.startswith('Y') and pol_name2.endswith('IMAGE')): # pragma: no cover - raise OSError("PSFEx: Expected POLNAME2 == Y*_IMAGE, got %s"%pol_name2) - if pol_ngrp != 1: # pragma: no cover - raise OSError("PSFEx: Current implementation requires POLNGRP == 1, got %d"%pol_ngrp) - if pol_group1 != 1: # pragma: no cover - raise OSError("PSFEx: Expected POLGRP1 == 1, got %s"%pol_group1) - if pol_group2 != 1: # pragma: no cover - raise OSError("PSFEx: Expected POLGRP2 == 1, got %s"%pol_group2) - if psf_naxis != 3: # pragma: no cover - raise OSError("PSFEx: Expected PSFNAXIS == 3, got %d"%psf_naxis) - if psf_axis3 != ((pol_deg+1)*(pol_deg+2))//2: # pragma: no cover - raise OSError("PSFEx: POLDEG and PSFAXIS3 disagree") - if basis.shape[0] != psf_axis3: # pragma: no cover - raise OSError("PSFEx: PSFAXIS3 disagrees with actual basis size") - if basis.shape[1] != psf_axis2: # pragma: no cover - raise OSError("PSFEx: PSFAXIS2 disagrees with actual basis size") - if basis.shape[2] != psf_axis1: # pragma: no cover - raise OSError("PSFEx: PSFAXIS1 disagrees with actual basis size") + # Not sure which of these are actually required in PSFEx files, but this implementation + # assumes these things are true, so if this fails, we probably need to rework some aspect + # of this code. + try: + assert pol_naxis == 2 + assert pol_name1.startswith('X') and pol_name1.endswith('IMAGE') + assert pol_name2.startswith('Y') and pol_name2.endswith('IMAGE') + assert pol_ngrp == 1 + assert pol_group1 == 1 + assert pol_group2 == 1 + assert psf_naxis == 3 + assert psf_axis3 == ((pol_deg+1)*(pol_deg+2))//2 + assert basis.shape[0] == psf_axis3 + assert basis.shape[1] == psf_axis2 + assert basis.shape[2] == psf_axis1 + except AssertionError as e: + raise OSError("PSFEx file %s is not as expected.\n%r"%(self.file_name, e)) # Save some of these values for use in building the interpolated images self.basis = basis diff --git a/tests/des_data/invalid_psfcat.psf b/tests/des_data/invalid_psfcat.psf new file mode 100644 index 0000000000000000000000000000000000000000..bbb279c0e32595e0c464403ec7f15c079563dc2f GIT binary patch literal 23040 zcmeIYd0bA-|M=g&B2m^*DDC@oX69U#c10yEQbb7+ttw=#sFbwOf-I%9Af&D{bFQn1 zEJ?CP_AN^xTiNMX?zj8ReRsQkzMt>o_s92P9(B$Be9gS(HFIX>99MfsX9pW)WmDx| zoSU+qvfI4C2<7fSUu9c+H&^9=z##u#O8%K-Y47H2KkXlQu$%WkbWRr2>|ML-^x8iC z|2fZQnwyQ2^*`|b?C;lJ2Kz=U`-O%?`UZyl(RcqPk^UhOfuSK0|Lw$A*ngwHL%%if zTLZr}@LL1FHSk*lzcuh%1OJa}pl3VjYCqY@RC%1Gy_1`TrGw45@4DQ*f41x1zjpsw zD0k;uxOggmEu*Y%>oVD~JJUwl!P>&gZSrIXSB;)}|G(~^4gcr+=db&5gI@N}1{{6; zp8dF`r<;wdva^kgvdd(T?=HU1fF*5EM zkEr0;{tJ~u1C)b8edqYkQH}_WTIlEh-I3@AwuP0IjkBALbuZ^PV`%gP&)3h-KP>WJ zh*oy?-PZEs`0DE-WNgeq##E2~0z-U*l&$6kel7MxTY8H)Pj>ifg1xj)S-EF^{I|b7 zXTk0xpmLb+Lf>HjNdJYs^|_lj!Nu9&pU2lZG$=YGG+24=!qBKN<$%zI%8@-M?7zYP z)7Y==bF?w&9^YxR>>Vv8*eL%vzE0h3=^2fmQvTcXZl0Ho%j9le_w>@$H#9afFocE( z0RtN?{hsld91sxUAE~^^Hz>;g-R95VQn+vAMCkjetfg%IsS7O`uAcnf7p2s>Z|Knq-;Ix z9KWA$#=0EHF))HgUwN*!$~^_XRrnXbhv#bH==?pedp!Ug4rTC_=jt0A78DpVS2-fm zKkSc*;;-CYOL>lOr0?J1-8uif9`wu)>%ibYE`fZ50wbgU71+(|xxxB6KmIDskn_X( z;veMSjgD0I4GQgU?$`P3yQF(*_`&^$_tjp0$movzFL*}Z>+etQkL&X1iBZ(GQY)a`?h6)#&H^5% zErAqw8#THx{1>YKv2D`Mvhnc%!-u|c1X^0khT*m<%6T81bze@m`=p=X&-~cDj%!danPQkmW z&yZS90`mWqg5s^Fq5QM%hz(UjrzLGsaqc6OSaSt=Sf`^wbSk{&<^z|RDT5AWbD$i) z>O1V8LF&c>5pb+=E;N*AgH>nVpuRFCXmZ9#w0XoXbj-93Jy&(0`vwNkgZzilYSWDA zA*IJ?A+dd^;gcNNV6g?6)GdTx`dx)t+8yA;x=d=@?Hhl>{}TX-tBs&U$R-$f>LHS_ zDMX6~v*<|V3-tVa7Cp?xhel0~bYPb~o%qC)POWO769X#g*#(ZYfv!0%d}%u>7EeUx z?Q7wyiwKF*ysiyKC-xTZ^& z#4o3BTvy>rzAfhtysOET7EPxwPdZO$E?z`i>)oYAjuoN}O`p;5U1>1uiVPGr5CtQb zYkiXg+(&o9apLpgDWO?Nt=SN5+Fge}Z0n+J)i%`tV6^(x9? z^U&)DlC-D$3%Viq9(RJLH@7B6!bHvXm`RdkwaL*uW0NbRX_FFLITH}0!_76OxFW0G z&~a{DTF%M~3Fa9iFNb9KaI6^go399FduW2RvK+WTq!ixQ2uIVx7oo$PTlDZnj`Z4A zJFfJAHf~nQJ(E#c@g`ZO#U?M`$(yR!elS(OHPz(q3TKm<5}UZ?7Af4mZPj$zj7nN1 zBm_02+o44QL)2#s1>?O7z@8B_$hFObnJPz+q_-@Z+q?~(=WEhhQ4Vy@yjk3lTum-} zZ-~h>GhvgakIAO$z{%96Y`dvOr>V)sHjasP`flzvbqlV9|4KT^s*@gfwH-CWpW2c&8G|0r_#{$JuNad9qq6UMw4p-VErd9j0m#@A*c6J z+CE3YrPHHezGWp6z2S_4mLYUD(wo-0zlF|!WWpUgaXhzJ<*3QP*Y`|pH!LD=1U8v%4kt!UvsmFf7nwAo{ zU|&9LOjJfnR|Tk~Rh<_5be8r~x=L3S4B?KQvy!_)@h*4km_lw=-8}B>PHXO<+bd{( zC!vGJe4-VHHlRy55^eNVMCucs!@}hsp~d-`ph)aP55LDzE!j~}!N~<~P3;fche{xK zpU>!mmJ6-eu1Zhe0O_KZ8v0&=K3CjvF;`@@HhnSg5WRV54edSQEj@S~L=WP zXkv&N5;2N^F*7g2zTwHhEcJ&q3uJ=LP|ImN#BDWbpp*mh>0^aXG>g!pgS%+`cU$OL zEwAX6DVvqNr5A~N(^I8X#TWx@n z=EWh0)<>xC6jQkWl?sf0T?^U|MNoP7!5>=s@fV=6@4(yaesG4J4lH%^M4~(vfo-

(sbCcH!YDs0i#x~pjQ{1XU#=tN_zp1>CeN@3amZ}gp1K>2YTJTT2RkA&PgSIBzXVB^-G#3kM#8k2JK^S_ z?NB&m062k?!RVLnKN5S7QEAUB!4|y_pyf&i9G9>FCOkO=i#|Srhl0n!lOg?J)A6Nn z<6{Xp?M@dQ8G8^Ac~g*Tw}3h@L#%h=Kc`ZUecplehc@6UwHk7SMB!k08K@VN(ETqB z#WSygvyD+;eR@9CRwnpo%CF^6qaC}bmsO@fQ+h41oY4d>Ndd+nc>;Jr5Y!h_Ddu4EVb@HS|!#&LcX zROyaQcqa#mB##)?BPF=P90#?!6@VmE6JWA52N z-7J!&LM4_mi_HCe zy1lFwhaso(2N471bX;vclXFtYof@_4@HaV3>AP2)+0p(?!Sj1WZmk7A8n%a|7zdE1 zqUWSpt$`$GNkE$}0Sw&~1yi@CqK$r2$S&=j{BiD{eET;Z>>KBc?3yR{SfQoUiOKa; zEHDVB3iDJMZ>i}$GK>ygc#Zpg9)?xMKEVeB1^D6lwFKOkAs-?}vD_CQSKRB$UqL?{l!53ZG~t2!+u-%CW|hzMQ>lTqoNat#@bmpamg!Q%!V7x z`-EoR8qQ>9(4Z;I6$=fVJF5e?X^tWzF76@|jyteQUH92goBnLU!CH3J=SGw!cbz_I zv4ksbw4YX2dLS71=$^ptLYzQqRUv=orW%%Pgsk?#m*i+&J$`e&hH=tKXWkxv$&?Sy zsjR$tk2$(-Gn4gd8WHcRCT@m7q~sjK%KN#qSwchkqKh}MIXd;Uu2V5rv@DwIQMQ@h zmDnMWtmk{aL4nloc6P2sgmT1x;shKpF)&!A7D@~#Y|fr!Cbm~g14>Jl@Xtw zh<7+XW{#|Pox&eOn~$Z#C{#+y0$G?4LGaFV&wxeRY_n+md8 z2`~wGOO96gk;*E-($)f&eR6|up!SHr-f)-R@@kT&*IzR>PX#Z`LX{ zZ|nhr53MG;Z=A@c6Jv;%yeDNjXaY{jea<`=(cw8dC*slKw{UfQF%I8;1+SUjuQ10Ozv}LI=Qn*t9w|xwP=^qb)B!d(j95a!Znq9)#H)S{zeYa|DIbc>M0gMEdvKxnfTSXrG01vG$>Eedey=U31wdjKxA1t9Ux z3Fh>jX6ALN6WG0AK9eQl^gX{P%n4KvHQv0-pBl1e8g^XT#QStY9qgL_5+7u?Q+r%T zK>PCH*jil_gx|Oh=bkSCbM`+0UQwA$`tze8!ucV^lUh%Gn4rqYhw|_cuVTt@{mj40 z=!rA2)+ecem~{;Md_0JYx=eNLy2YpmwL>{aEhCSaFLCiOGq65mH8@zK#z+omVVd8* zquRsYRt$Eu!29hTLF|FU%-LIWc`F@{_QwB~X54^RQAf3gbG}G9V^x?94A+`N=^^Xz zWI7Xzb57%>rsllVL%CqnlLz>M{smF z^V&F;nJXMl1qzje6q!pnRz(WSF$&bsyiPph#cdq((Gp{+tGvgDgn(o+&AI9^i+Aae zLvOsFrEzp3TzR?iK0q9PX4*8XDJ{cSltW)0)7UP{8?94-W%Xh!#RvCe);`Gk8Ru8> zcw?t`Fk)-MnRykNl>7LLRMF1UoCO#3d7FIxJj477=VxVjTcj5=dWKeDr-VPx7j2*f z(IUVTUgxxrU-+|nzmf+mMTat$c3(hWrB-}x;6AFn#FY_KTt-s#MR9>r1QR8_orxD2 z#2Hkg{wuui%NQqUbbw9#8t_C`g^d09fN+~CafFgEWu0b6WLC~1c@~(IDO@F7zg5gb zkrJXiBm_U6bq0HG+4w!@pTf*z^JM_t*~N)A>>}>(voRkf;)A*gP`y!@8Z*6`6BNn>0M zNeZz5fz^C`!gM%{b8sS)Uh9%)LMm`@ODh<(Vb@>fV8zrGkh3TPzP)b=3remKeyJTR zM4y6|sT*)!+;G@-zy>gj{3v?sP?B|EF0o&~oQx@Izz2l^sa=*s)^!+@GgwMGv|(u1k#NKxWBFysE(TmWunHCi?92T8z!tExa&QH&K5 zTp*nl=i#TeTr|0`j!3<)z+;NUNUO6PIk;UPENc7&mJgf>cuyUf!&fhV>kAZ{VgU#H z0z%eCkx2Eq)MMMZ%-u2(cDR)Q+?%%?Ek9<4LN*4I1EVVV!;-q#^+jf6UhyoHy^5wK zWiO)(h4WFAc{n@#=t?-<@Hia%b`w5cTa6ncw)DuLM%wKJ3NvD%$Ad$}`>-yETXhSX zcehjSrYfs_E}87Q9EH0cl%uW_?`d-S3#~Lgmmf4>2fu7n1X7wXl-}xK#=>915YOLIbo}vj94E zR^vrchp8EXn}L{&B{ zfID|U<2nB2H81%mUY|iH*ZI<80#v6-nR zZ`A@lSbX0a^_hASo=!c)zI;4J=LlK7aVwE|dV#V{{QxS=I?1Pb z0VGjmH#~SO8cYg31#jK6#)X=bKv8WV?J%z&-=_MkfMaKZcA4DeDk+E2r&o*6DHdbV z`nnOQkA*MN^PA1C3{-*gm-Z16t2vOXY)CmK+`v2b+j6Ww z6)JD1(ex25@SOcyoMvZ(?tkX6j?L5f$&)16lg>_b>c|qh@Y!B;Zde)`f71YswI4;E z&YI7vO2)v?+*8ceTklAaf)n97gkVW`PLKV9V6l;;<61PS4*r5hA8aM{`x{8@(_l0j zpG6w-eMwnXDJv*k#%A5DCUTFr)1#IhKrxHYBk7HHXf&LUfO0f`*FFdismOpHA15I> zG6?2`hf&7^HGB9yj%qOrBXbW~lDl>Xv4_x2kT(O6nrJ7`y`Atb((!{alg;G_GeNR;$cfa4F7q3^fi>dPm-=l6tx;nw%S;^h;#s_Y50eP4}tyT~QebM`9@3h%9zPurgM=6Hi0U0-a>llZWLG+qe!O#JOZo|- zH93y5Oxy8S89i~lXM_cQ5h)ESx}Jje1xIk~!KXyQVK;b_zMom*5KLY@nZvHv+f9o1 zisDnTM`7X3qh!+v3U|Hgen&7X7mt+p!?DV?oRP&FdgFgf!wWqx6XlDZ#8e}jIXr;_ z?bq!Dg{Ll%8MZRuzP}k9Wqk!URtezhvIivO<5Rds#hVmJeTJJ?R4^3^#S96T{Kop3 z&$+QPp(5?sH7b2@4R~C2fozB!L-d#%)ZVpbWZc0cr1tn8c>Z2GffP|8YLECxCdT|7=gOl0 z^*tOj`N2Y7Ly{2Zk!&ZBeJscL*Je^L?tG-|RKGqSdTZbRG>wGUxwZOKZ;aY+pby+0anuUZMk z54M54PkPjWTT7_NRfDPat$jGe@t5|CRBGBAj{nCMVAvy;(r{sH=~-U3=SuK=xLOL_FCSn4Y83>bRtr(K|u{CG_DbvaIB_jy3u zX&lEt=_RPqlLx7kJ3eTaKpbUkacHy$Ovs)B4_}Oj`NP}bj39leIchajH*^K(okmiM z)q^;VD|+ z2iL)~)z^`p&j?s+(*e(&&WDAcw85fLk0_G?w?K)&o-=*ZlD~F{^4R9YIk5F1)w0GN zY+SM)3QG(IMh5%wBk_H>Zs`=TD4fTs+OY}duQ`rppBRHy2}86aa}bIua6-cjOdwDH z4b<@01El>jb?vAZr51eidl|-^vJ}-a#+a&4yMTu@d}Iz;7{c+BqAP3I!KA2f0J*lc zj%@!>L}V0Ik=X2Y2%Cx0Dmh`a(%wbrzah>}aXZK6-FVD8Ek4=({@jRm zOTA6!KHoT*{`) zDvIcStIUwi5pj5dPJ($IQBb7cUf#rc`@r~v!A!tJ<&j5?0LQv=y z4CfngDVGNy!0`(!F_W0a7UgR3Ll27ZAG|i>-#$5+Xg0ZXr9F>uHP1HCJLc|1NAqHl z&b&qNMwBt!PkX?Wi+)(6oZ-+9)G2ApExf$*xzsohN08XoNDa1912>{$z^l8?cz(SC z477;>f_6bQ6mrcL?Kb(kwia*0Q)btUPy z{X4iw(+$?eWeWOVl@#@<;^gQO`lF%6?FPwiWCvFDEv~w2;sIzI5f9c>0an9#p^C zkDoPrI6v3*AuA9yCjrBkz+BC%Fcm2g@92RWu~-h3p3VWTJH8OrTbn`2#TCZonj8-Q*w^=_QK^bo65Ooa_(vbDu()pi@xlr7yfYuMZJf=f_TBcaoQfv`A5@3mJO1iHsU7 zL?k`p;l##ls&;!6D6x|VV?OL>L#q1wbqc(yVJ&RevTEYTF=-CwmEYv*cl+GqLz|FTNZ2dDz&>1EXOel|es zX$TeiWe@M#ixJ;s^!U8$abi^7-jU4UeY2?%bCyujOZ`BNQy!;zwE>R1CkBs|YC^>W zTYzqV$c&4P#w~-S@t3qtGBbEQ4C#0RWdv$?t5q=&PFRW4m$p#~I$!VFzn8&z_*eiW z2gXw$-t=Qsj6H~`<_>&qO9{ARGZ8FMx5Y8T`5^yoF&rLN3-VWd1fy2_f+ibNF#rBp zGM`h$NxnXqnWZHKDh?P>qQN4}u1Dv-=YI=xhAV7kQa9RGWWE%`&3$g;b4hu;mWx+F zl1UxI+BtxiMn{-e53k|5!&&g?k{axH)e%$_GTseDp{H}j-<1<~uM4o7dwd&U!H#Q6!-^gsiA>8%}<+F8fMd=$dT zV!2?pd<~UN6);@QiM?_En#MStA4zR~luTux8-Ur1Z+T$p4oZB=Bj#?VHajigJw9<)kaM;5Zd=${5_j z!s$+!Qya${dwzPQUrHl&*XyVI+kcbOlh2b**vn~=GN&du)pN4h9aub3$V-9*XGD}K-I<~(8$3`m-V?kvfBAVa~4rptT z{MhkeXO=2x`5b}I9rj@ck%gSc8TUC}6MKE<|1%kt&$^yqp~5)4f%A!ayFZh8SUrzy zk8h$n%*=^G+g=!Yp_L3y_zcYw>hTm?Wo*N@j&PUh2DC46Tw z1x%99fN2Y};41GYxcuZbe0@nUOh0m%dHq1Z>TQ-FS=Aj>#>sr@jNV%CR^1cRRCM>A zV9qyIZ$9Tl+7swirHS(`P7=52B4p4kF+#oE3!3k@!wsE&9JkRjc=LcD!tXnjjnA+l zb95%+4;N;UBndCJZy<-HubPX$EC$#@sMq^aYUVo^(0=nBc=-yEr7PAESH(p{j8{&c ze;iM;%!*j`_h-oSuzhUeRW){^Obot$dM6up%$pq`CqljI*%81?5rok3J zf5N5?5913>lxAa-P1*4k#jN{*i)7$hd7e@1J2KYtF!eaV{CgR^hUK9|Az%gxUp$*h z8&b|oNDLsg0cTl};(TyF+KLq~J;G*g2w`(0blBXOiELE=K79G7%6v2T+kA~XO)T%w zEOybOW@7DfgFLGb?S9AjnwXv#O3i)O^i2*Caauu4>_ee#tTafQDx~ z15&17oK-H~m%qJ72JieRA2PbXJbo5e12u#_pu~rVFncJ#?;1Oi$t`W7vwRp|vNM8j z5q^#z@90gOO5V}w+9l}Si%$OCFOvk)oNN4LpE~)XU9If);xL@jd682S;1Bg&9$<}h zF-mwr3$@x-AFm14!|j_NL-Wghh+2U(Y!l7H?`M8MlLp*DW&Kjv`e1p!Pv3q5nK|w3 zQGIc)-ba0|L5mH4AIugEF=*p&TmFf!c*>Nm6-y?M_uYhwgU0~txX1X4)lM*9rau0{ zEtq|qKfHe?|8B}F{t6jM_O`YrEBmgNm0b~w`y?rWE4w0y*k@5*`TMs#5v%=hlGS{& zU}Xs_8>9;l+R~UPhQXF2DyU)D5&C@l1FpJ~7FT122ETyQDj2kF6aCumB3HlT5US@N zu4FCd zO0f$&_ON2w_t*_OPuMxl7~I+L76%G-0M7dz%)WIVBri$>?@-r+su_~ReqfE!vgqxI zBW{eEV!P<9x3{_S!Q1JNuaelz^fZ3`?KbxODtB7u#Z6e1nU6mi*sufY=CitckDy@9 zw@|^u4t!YU4@^C;;fu!svvY$pGgQI{*gASJ2bSE#9lEFChPHb|t7Q*4Ab$=OA3jMB z&DW$eB2S|Ix?22JB-K`h=)5=TfheQ85RRPLm#pS2+SOSE=cco@b5rG?PYLRDsBi!y4O(s|kff4EpiL_i9HW{PEkuCl6?1_)A zjl@u8G~V`L5V^N=Ig}H4n_a6hY#%QVF!*P5Mn5 zfz@=N06#^&YoL$eUi#pGsNXZH8wzdjOYb?YKBNcvy_p>q_UU~%QWaSt}-{dJ) ziJ1fn%d;z6!ep7#wqL(h_*vaw&0`iUTSnD=xDB-SW%F$N(3JA|#~k?rb*gCJ*E#&F zoZiY&k5(_h8$|nnD+w2XM2Z;HxG^0@jyiz1d9;CP#t;zbfqXzg5*|e zN=Nk1xZm;^_o-GO$3_VDb(Ev*XJiA3KIftPmAkOGxgEY3y9#$0zJ!6sL*N0KU~oq) zAH+E&!BJC3fO9jPVE>1MK-+@N}Y!d%#}fpAXmBxoP1IWC(Gu+HyxsI(?A-Q zp1%S&Uml03?K7dI^b2IzFBQoinu$j2pAC1Yw8Bj#I#7_Y3$$sQ0QuaVz45W+D`7C# z{uwWJyeCIV`4Q}&vkn&Rdx`peDMiWf4NO&C1@W#Nw7YmY5{W&5lFn42%I$Yiz%W&` zZ zIF0rR?N39lIl5&jgE-Ema7KtCj7bmc=HyY<1$lV(hd;lQ0YSx!p-O`*R5U$>R25U; za*c8DK}jTncLA}`9ZYH$N_Xhh9bR&IzgjRsi;}} zAT)TD&Aj(4hZp>(!_f|{-S-;-J$>PH-aiHOE5~!HCNN|Yw+Kui!9Zo%8?>l_Lifg; zqovee(5rZ+T#4!Nw5+zVK=}Fzfp!SXAFXagdv6t`vl|f|d_$P-`}QUB$v8lAb{dhP zd)E=&^AF$<^%#zg)m$xI@(xOFtuD*a8C7+V6LSQDqMPnu2#B7d#raxchBucNv<2o>fM(Zp*v%FO)dMF zV+9cRgJT%EyX#>0SK4rVMFHv)B?s;1^&@f}uRxNY2DUtZk4-whllCt@!HpMA<{l28 zPdoYg3gV|c78qQYrDMux(N*hX=~&~LsGXzU{T}ijJXUiDj1e!xyJG60b-Nz3lRJ>A zUU`FnI$`iu@C2%D6d|(q(y-KI0T^VS$~N`A#^1GnJ3paZm#dte$t})S;V!>Wk4(HE ztr2gD)n{s>-L9_4V~Hi;^s6I)PQcenr70VF3w8=#hLhSfVB%+O zxMjvAYS0-o@T&bJ!SXq5t?XKUv*AcVxW_!fqRYnovT2=Yr7(}4C6UY>Iba%B$}ycz zJoA{A9JUDUeyIs}JbR1gKeL2);wyP)E`(v1^;7ZfRTGWty_GRTN5RGJ6F|B1PW<-r zRj}zn4ryC3gRQox;8)m$3Fwvc1ZxtP3TCT4=Th&0lzY4PW_%O~heh5vDVD@(w?n`>hY(d}`12Px!)~ImC9j1%7*K0Ju2)47q2~NKWbL!-=Dx zku$E(NQ(Se(D=HF9DTT(e2_Or=+$%L{W*qJXuX7k>oI=XH;$5jp$!fU6Ju1{9)4qg z?=$+NYw;(eJNSsKJ`?}Aj0mibvE101MCalupyzdn7$4n1eDBYpRC0C`{_qyKb3K=s zTUp~wk?nAJu?3~|G>BRD{w3pLRQ)~ouVJh?WIPsT8yWuUr#RyNMj$OWnQ55k0oC3~ zfwB5>#M*TW)b?z~j}nA9FRnv4N-ByR9$UcNTFL;OX?v*$oFji__7-QT@q56a6XxJl zK?$SZeczsDqQay;I!5Nde~jm-mlBarQ;Bf*cLa$S%b;;zDLCSTIheGsyz}n znE#Y!TsDQztnQzMb-M2;_at5e4=0Qy0~B2GgxUQ8yX7nx_&|{f+3o?_J+1zU@vG^S z;+u6;iF5|A*nE&vp)SPCH#lOPq1QrfTleT!8NbW@*1&HK{MNv44gA)?Zw>s`!2gRH G`2PU>MTp1% literal 0 HcmV?d00001 diff --git a/tests/test_des.py b/tests/test_des.py index 381d0b9e39b..24a9c1baa82 100644 --- a/tests/test_des.py +++ b/tests/test_des.py @@ -642,9 +642,17 @@ def test_psf(): err_msg="no-wcs PSFEx shape.g2 doesn't match") with assert_raises(TypeError): + # file_name must be a string. galsim.des.DES_PSFEx(psf, wcs=wcs_file, dir=data_dir) with assert_raises(galsim.GalSimError): + # Cannot provide both image_file_name and wcs galsim.des.DES_PSFEx(psfex_file, image_file_name=wcs_file, wcs=wcs_file, dir=data_dir) + with assert_raises((IOError, OSError)): + # This one doesn't exist. + galsim.des.DES_PSFEx('nonexistant.psf', wcs=wcs_file, dir=data_dir) + with assert_raises(OSError): + # This one exists, but has invalid header parameters. + galsim.des.DES_PSFEx('invalid_psfcat.psf', wcs=wcs_file, dir=data_dir) # Now the shapelet PSF model. This model is already in sky coordinates, so no wcs_file needed. fitpsf = galsim.des.DES_Shapelet(os.path.join(data_dir,fitpsf_file)) From 24bd2f975950ec7cda84f219fb4906aff4bd8cd8 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 15:21:18 -0400 Subject: [PATCH 88/96] Switch combination of index and n_random from warning to error (#755) --- galsim/scene.py | 3 ++- tests/test_scene.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/galsim/scene.py b/galsim/scene.py index 3406cd5fa2b..bbbd9d8ba78 100644 --- a/galsim/scene.py +++ b/galsim/scene.py @@ -459,7 +459,8 @@ def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size index = self.selectRandomIndex(n_random, rng=rng) else: if n_random is not None: - galsim_warn("Ignoring input n_random, since indices were specified!") + raise GalSimIncompatibleValuesError( + "Cannot specify both index and n_random", n_random=n_random, index=index) if hasattr(index, '__iter__'): indices = index diff --git a/tests/test_scene.py b/tests/test_scene.py index 5a3495aec3c..90580db7c2b 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -278,6 +278,7 @@ def test_cosmos_random(): n_random = 3 with assert_warns(galsim.GalSimWarning): + # Warns because we aren't using weights objs = cat_param.makeGalaxy(rng=galsim.BaseDeviate(use_seed), n_random=n_random) with assert_warns(galsim.GalSimWarning): inds = cat_param.selectRandomIndex(n_random, rng=galsim.BaseDeviate(use_seed)) @@ -326,6 +327,10 @@ def test_cosmos_random(): ud2.discard(n_rng_calls) assert ud()==ud2(), '_n_rng_calls kwarg did not give proper tracking of RNG calls' + # Invalid to both privide index and ask for random selection + with assert_raises(galsim.GalSimIncompatibleValuesError): + cat_param.makeGalaxy(index=(11,13,17), n_random=3) + @timer def test_cosmos_deep(): """Test the deep option of makeGalaxy""" From cafc3801f95a16e1216c68308ead11165b7958b1 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 15:23:14 -0400 Subject: [PATCH 89/96] Add profile context to make it quicker to profile bits of test code (#755) --- tests/galsim_test_helpers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/galsim_test_helpers.py b/tests/galsim_test_helpers.py index c8de6ccba1e..fe69c0e0e35 100644 --- a/tests/galsim_test_helpers.py +++ b/tests/galsim_test_helpers.py @@ -663,3 +663,21 @@ def assert_warns(wtype, *args, **kwargs): del Dummy del _t + +# Context to make it easier to profile bits of the code +class profile(object): + def __init__(self, sortby='tottime', nlines=30): + self.sortby = sortby + self.nlines = nlines + + def __enter__(self): + import cProfile, pstats + self.pr = cProfile.Profile() + self.pr.enable() + return self + + def __exit__(self, type, value, traceback): + import pstats + self.pr.disable() + ps = pstats.Stats(self.pr).sort_stats(self.sortby) + ps.print_stats(self.nlines) From aba90f3d12862222e32b5bc55e88f30538e6e005 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 15:24:05 -0400 Subject: [PATCH 90/96] Add coverage of some warnings that weren't being covered (#755) --- galsim/phase_screens.py | 8 ++++---- galsim/table.py | 2 +- tests/.coveragerc | 14 +------------- tests/test_correlatednoise.py | 31 +++++++++++++++++++++++++++++++ tests/test_detectors.py | 5 +++++ tests/test_phase_psf.py | 31 +++++++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 18 deletions(-) diff --git a/galsim/phase_screens.py b/galsim/phase_screens.py index 819dde1c388..4274411e63d 100644 --- a/galsim/phase_screens.py +++ b/galsim/phase_screens.py @@ -215,12 +215,12 @@ def instantiate(self, kmin=0., kmax=np.inf, check=None): if check is not None and not self._suppress_warning: if check == 'FFT': if self.kmax != np.inf: - galsim_warn("Instantiating AtmosphericScreen with kmax != inf may yield " - "surprising results when drawing using Fourier optics.") + galsim_warn("AtmosphericScreen was instantiated for photon shooting. " + "Drawing now with FFT may yield surprising results.") if check == 'phot': if self.kmax == np.inf: - galsim_warn("Instantiating AtmosphericScreen with kmax == inf may yield " - "surprising results when drawing using geometric optics.") + galsim_warn("AtmosphericScreen was instantiated for FFT drawing. " + "Drawing now with photon shooting may yield surprising results.") # Note the magic number 0.00058 is actually ... wait for it ... diff --git a/galsim/table.py b/galsim/table.py index 6e32cbb291d..2d805d518a6 100644 --- a/galsim/table.py +++ b/galsim/table.py @@ -270,7 +270,7 @@ def from_file(cls, file_name, interpolant='spline', x_log=False, f_log=False, am try: # version >= 0.20 from pandas.io.common import CParserError - except ImportError: # pragma: no cover + except ImportError: # version < 0.20 from pandas.parser import CParserError data = pandas.read_csv(file_name, comment='#', delim_whitespace=True, header=None) diff --git a/tests/.coveragerc b/tests/.coveragerc index 5419b6c58c7..94ab1652641 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -4,9 +4,6 @@ branch = True include = *galsim/* omit = - # Tests here require the DM stack, so don't include them in travis coverage tests. - *lsst/* - # These are mostly still tested, but we don't really care if the tests are complete. *deprecated/* @@ -28,14 +25,6 @@ exclude_lines = # If you put this in a comment, you can manually exclude code from being covered. pragma: no cover - # Don't complain about missing debug-only code: - logger.debug - - # Don't complain about not hitting warning code - if suppress_warnings is False: - import warnings - warnings.warn - # Don't complain if non-runnable code isn't run: if False: if 0: @@ -45,6 +34,5 @@ exclude_lines = except .*KeyboardInterrupt except .*OSError - # Or code for special cases of older versions of things. + # Or checks for alternate versions when some package is not available except ImportError - if .*pyfits_version diff --git a/tests/test_correlatednoise.py b/tests/test_correlatednoise.py index cff81ad4253..8978b3d350c 100644 --- a/tests/test_correlatednoise.py +++ b/tests/test_correlatednoise.py @@ -710,6 +710,36 @@ def test_copy(): do_pickle(cn, drawNoise) do_pickle(cn) +@timer +def test_add(): + """Adding two correlated noise objects, just adds their profiles. + """ + rng = galsim.BaseDeviate(1234) + cosmos_scale = 0.03 + ccn = galsim.getCOSMOSNoise(rng=rng) + print('ccn.variance = ',ccn.getVariance()) + ucn1 = galsim.UncorrelatedNoise(variance=5.e-6, scale=cosmos_scale) + print('ucn1.variance = ',ucn1.getVariance()) + ucn2 = galsim.UncorrelatedNoise(variance=5.e-6, scale=1.) + print('ucn2.variance = ',ucn2.getVariance()) + + sum = ccn + ucn1 + print('sum.variance = ',sum.getVariance()) + np.testing.assert_allclose(sum.getVariance(), ccn.getVariance() + ucn1.getVariance()) + + with assert_warns(galsim.GalSimWarning): + sum = ccn + ucn2 + print('sum.variance = ',sum.getVariance()) + np.testing.assert_allclose(sum.getVariance(), ccn.getVariance() + ucn2.getVariance()) + + diff = ccn - ucn1 + print('diff.variance = ',diff.getVariance()) + np.testing.assert_allclose(diff.getVariance(), ccn.getVariance() - ucn1.getVariance()) + + with assert_warns(galsim.GalSimWarning): + diff = ccn - ucn2 + print('diff.variance = ',diff.getVariance()) + np.testing.assert_allclose(diff.getVariance(), ccn.getVariance() - ucn2.getVariance()) @timer def test_cosmos_and_whitening(): @@ -1281,6 +1311,7 @@ def test_covariance_spectrum(): test_output_generation_rotated() test_output_generation_magnified() test_copy() + test_add() test_cosmos_and_whitening() test_symmetrizing() test_convolve_cosmos() diff --git a/tests/test_detectors.py b/tests/test_detectors.py index 6804cdc5487..45c3fb4f20e 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -256,6 +256,11 @@ def test_recipfail_basic(): im_new.array,im.array*(1+alpha*np.log10(im.array/(exp_time*base_flux))),6, err_msg='Difference between power law and log behavior') + # If input image has negative values, then raise a warning. + im_new.setValue(30, 30, -100) + with assert_warns(galsim.GalSimWarning): + im_new.addReciprocityFailure(alpha=alpha, exp_time=exp_time, base_flux=base_flux) + @timer def test_quantize(): diff --git a/tests/test_phase_psf.py b/tests/test_phase_psf.py index a7f33c37217..e11834c02be 100644 --- a/tests/test_phase_psf.py +++ b/tests/test_phase_psf.py @@ -524,6 +524,19 @@ def test_stepk_maxk(): psf3.centroid psf3.max_sb + # If we force stepk very low, it will trigger a warning when we try to draw it. + psf4 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, + _force_stepk=stepk2/3.5) + with assert_warns(galsim.GalSimWarning): + psf4._prepareDraw() + + # Can suppress this warning if desired. + psf5 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, + _force_stepk=stepk2/3.5, suppress_warning=True) + with assert_raises(AssertionError): + with assert_warns(galsim.GalSimWarning): + psf5._prepareDraw() + @timer def test_ne(): @@ -879,6 +892,23 @@ def test_speedup(): print("Time for geometric approximation draw: {:6.4f}s".format(t1-t0)) assert (t1-t0) < 0.1, "Photon-shooting took too long ({0} s).".format(t1-t0) +@timer +def test_instantiation_check(): + """Check that after instantiating, drawing with the other method will emit a warning. + """ + atm1 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2) + psf1 = atm1.makePSF(lam=500.0, diam=1.0) + psf1.drawImage() + with assert_warns(galsim.GalSimWarning): + psf1.drawImage(method='phot', n_photons=10) + + atm2 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2) + psf2 = atm2.makePSF(lam=500.0, diam=1.0) # exptime = 0, so reasonable to draw w/ FFT + psf2.drawImage(method='phot', n_photons=10) + with assert_warns(galsim.GalSimWarning): + psf2.drawImage() + + @timer def test_gc(): """Make sure that pending psfs don't leak memory. @@ -944,4 +974,5 @@ def test_gc(): test_input() test_r0_weights() test_speedup() + test_instantiation_check() test_gc() From 6697f88493d6454cf5576b44f23ca65b14ae2c27 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 16:30:08 -0400 Subject: [PATCH 91/96] Use LoggerWrapper in real.py for logger statements (#755) --- galsim/real.py | 89 ++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 53 deletions(-) diff --git a/galsim/real.py b/galsim/real.py index c594e7338a1..914ecfd5cf8 100644 --- a/galsim/real.py +++ b/galsim/real.py @@ -205,6 +205,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, from .correlatednoise import UncorrelatedNoise, _BaseCorrelatedNoise from .interpolatedimage import InterpolatedImage from .convolve import Convolve, Deconvolve + from .config import LoggerWrapper if rng is None: rng = BaseDeviate() @@ -217,13 +218,14 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, "Cannot supply a flux and a flux rescaling factor.", flux=flux, flux_rescale=flux_rescale) + logger = LoggerWrapper(logger) # So don't need to check `if logger:` all the time. + if isinstance(real_galaxy_catalog, tuple): # Special (undocumented) way to build a RealGalaxy without needing the rgc directly # by providing the things we need from it. Used by COSMOSGalaxy. self.gal_image, self.psf_image, noise_image, pixel_scale, var = real_galaxy_catalog use_index = 0 # For the logger statements below. - if logger: - logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) + logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) self.catalog_file = None self.catalog = '' else: @@ -253,25 +255,21 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, raise GalSimIncompatibleValuesError( "No method specified for selecting a galaxy.", index=index, id=id, random=random) - if logger: - logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) + logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) # Read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors. self.gal_image = real_galaxy_catalog.getGalImage(use_index) - if logger: - logger.debug('RealGalaxy %d: Got gal_image',use_index) + logger.debug('RealGalaxy %d: Got gal_image',use_index) self.psf_image = real_galaxy_catalog.getPSFImage(use_index) - if logger: - logger.debug('RealGalaxy %d: Got psf_image',use_index) + logger.debug('RealGalaxy %d: Got psf_image',use_index) #self._noise = real_galaxy_catalog.getNoise(use_index, self.rng, gsparams) # We need to duplication some of the RealGalaxyCatalog.getNoise() function, since we # want it to be possible to have the RealGalaxyCatalog in another process, and the # BaseCorrelatedNoise object is not picklable. So we just build it here instead. noise_image, pixel_scale, var = real_galaxy_catalog.getNoiseProperties(use_index) - if logger: - logger.debug('RealGalaxy %d: Got noise_image',use_index) + logger.debug('RealGalaxy %d: Got noise_image',use_index) self.catalog_file = real_galaxy_catalog.getFileName() self.catalog = real_galaxy_catalog @@ -284,8 +282,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, x_interpolant='linear', gsparams=gsparams) self._noise = _BaseCorrelatedNoise(self.rng, ii, noise_image.wcs) self._noise = self._noise.withVariance(var) - if logger: - logger.debug('RealGalaxy %d: Finished building noise',use_index) + logger.debug('RealGalaxy %d: Finished building noise',use_index) # Save any other relevant information as instance attributes self.index = use_index @@ -309,8 +306,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, self.original_psf = InterpolatedImage( self.psf_image, x_interpolant=x_interpolant, k_interpolant=k_interpolant, flux=1.0, gsparams=gsparams) - if logger: - logger.debug('RealGalaxy %d: Made original_psf',use_index) + logger.debug('RealGalaxy %d: Made original_psf',use_index) # Build the InterpolatedImage of the galaxy. # Use the stepk value of the PSF as a maximum value for stepk of the galaxy. @@ -322,8 +318,7 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, calculate_stepk=self.original_psf.stepk, calculate_maxk=self.original_psf.maxk, noise_pad=noise_pad, rng=self.rng, gsparams=gsparams) - if logger: - logger.debug('RealGalaxy %d: Made original_gal',use_index) + logger.debug('RealGalaxy %d: Made original_gal',use_index) # Only alter normalization if a change is requested if flux is not None or flux_rescale is not None or area_norm != 1: @@ -341,13 +336,11 @@ def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, # Initialize the _sbp attribute self._conv = Convolve([self.original_gal, psf_inv], gsparams=gsparams) self._sbp = self._conv._sbp - if logger: - logger.debug('RealGalaxy %d: Made gsobject',use_index) + logger.debug('RealGalaxy %d: Made gsobject',use_index) # Save the noise in the image as an accessible attribute self._noise = self._noise.convolvedWith(psf_inv, gsparams) - if logger: - logger.debug('RealGalaxy %d: Finished building RealGalaxy',use_index) + logger.debug('RealGalaxy %d: Finished building RealGalaxy',use_index) @classmethod def makeFromImage(cls, image, PSF, xi, **kwargs): @@ -543,12 +536,14 @@ class RealGalaxyCatalog(object): # So skip any other calculations that might normally be necessary on construction. def __init__(self, file_name=None, sample=None, dir=None, preload=False, logger=None, _nobjects_only=False): + from ._pyfits import pyfits + from .config import LoggerWrapper + if sample is not None and file_name is not None: raise GalSimIncompatibleValuesError( "Cannot specify both the sample and file_name.", sample=sample, file_name=file_name) - from ._pyfits import pyfits self.file_name, self.image_dir, self.sample = _parse_files_dirs(file_name, dir, sample) with pyfits.open(self.file_name) as fits: @@ -601,7 +596,7 @@ def __init__(self, file_name=None, sample=None, dir=None, preload=False, self.saved_noise_im = {} self.loaded_files = {} - self.logger = logger + self.logger = LoggerWrapper(logger) # The pyfits commands aren't thread safe. So we need to make sure the methods that # use pyfits are not run concurrently from multiple threads. @@ -655,15 +650,13 @@ def preload(self): a big speedup if memory isn't an issue. """ from ._pyfits import pyfits - if self.logger: - self.logger.debug('RealGalaxyCatalog: start preload') + self.logger.debug('RealGalaxyCatalog: start preload') for file_name in np.concatenate((self.gal_file_name , self.psf_file_name)): # numpy sometimes add a space at the end of the string that is not present in # the original file. Stupid. But this next line removes it. file_name = file_name.strip() if file_name not in self.loaded_files: - if self.logger: - self.logger.debug('RealGalaxyCatalog: preloading %s',file_name) + self.logger.debug('RealGalaxyCatalog: preloading %s',file_name) # I use memmap=False, because I was getting problems with running out of # file handles in the great3 real_gal run, which uses a lot of rgc files. # I think there must be a bug in pyfits that leaves file handles open somewhere @@ -679,19 +672,16 @@ def preload(self): def _getFile(self, file_name): from ._pyfits import pyfits if file_name in self.loaded_files: - if self.logger: - self.logger.debug('RealGalaxyCatalog: File %s is already open',file_name) + self.logger.debug('RealGalaxyCatalog: File %s is already open',file_name) f = self.loaded_files[file_name] else: self.loaded_lock.acquire() # Check again in case two processes both hit the else at the same time. if file_name in self.loaded_files: # pragma: no cover - if self.logger: - self.logger.debug('RealGalaxyCatalog: File %s is already open',file_name) + self.logger.debug('RealGalaxyCatalog: File %s is already open',file_name) f = self.loaded_files[file_name] else: - if self.logger: - self.logger.debug('RealGalaxyCatalog: open file %s',file_name) + self.logger.debug('RealGalaxyCatalog: open file %s',file_name) f = pyfits.open(file_name,memmap=False) self.loaded_files[file_name] = f self.loaded_lock.release() @@ -713,8 +703,7 @@ def getGalImage(self, i): """Returns the galaxy at index `i` as an Image object. """ from .image import Image - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Start getGalImage',i) + self.logger.debug('RealGalaxyCatalog %d: Start getGalImage',i) if i >= len(self.gal_file_name): raise GalSimIndexError('index out of range (0..%d)'%(len(self.gal_file_name)-1),i) f = self._getFile(self.gal_file_name[i]) @@ -730,8 +719,7 @@ def getPSFImage(self, i): """Returns the PSF at index `i` as an Image object. """ from .image import Image - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Start getPSFImage',i) + self.logger.debug('RealGalaxyCatalog %d: Start getPSFImage',i) if i >= len(self.psf_file_name): raise GalSimIndexError('index out of range (0..%d)'%(len(self.psf_file_name)-1),i) f = self._getFile(self.psf_file_name[i]) @@ -755,8 +743,7 @@ def getNoiseProperties(self, i): as a tuple (im, scale, var). """ from .image import Image - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Start getNoise',i) + self.logger.debug('RealGalaxyCatalog %d: Start getNoise',i) if self.noise_file_name is None: im = None else: @@ -764,15 +751,13 @@ def getNoiseProperties(self, i): raise GalSimIndexError('index out of range (0..%d)'%(len(self.noise_file_name)-1),i) if self.noise_file_name[i] in self.saved_noise_im: im = self.saved_noise_im[self.noise_file_name[i]] - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Got saved noise im',i) + self.logger.debug('RealGalaxyCatalog %d: Got saved noise im',i) else: self.noise_lock.acquire() # Again, a second check in case two processes get here at the same time. if self.noise_file_name[i] in self.saved_noise_im: # pragma: no cover im = self.saved_noise_im[self.noise_file_name[i]] - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Got saved noise im',i) + self.logger.debug('RealGalaxyCatalog %d: Got saved noise im',i) else: from ._pyfits import pyfits with pyfits.open(self.noise_file_name[i]) as fits: @@ -780,8 +765,7 @@ def getNoiseProperties(self, i): im = Image(np.ascontiguousarray(array.astype(np.float64)), scale=self.pixel_scale[i]) self.saved_noise_im[self.noise_file_name[i]] = im - if self.logger: - self.logger.debug('RealGalaxyCatalog %d: Built noise im',i) + self.logger.debug('RealGalaxyCatalog %d: Built noise im',i) self.noise_lock.release() return im, self.pixel_scale[i], self.variance[i] @@ -1017,12 +1001,16 @@ def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng= from .bounds import BoundsI from .interpolatedimage import InterpolatedImage from .correlatednoise import _BaseCorrelatedNoise + from .config import LoggerWrapper + if rng is None: rng = BaseDeviate() elif not isinstance(rng, BaseDeviate): raise TypeError("The rng provided to ChromaticRealGalaxy is not a BaseDeviate") self.rng = rng + logger = LoggerWrapper(logger) # So don't need to check `if logger:` all the time. + # Get the index to use in the catalog if index is not None: if id is not None or random: @@ -1040,19 +1028,15 @@ def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng= else: raise GalSimIncompatibleValuesError( "No method specified for selecting a galaxy.", index=index, id=id, random=random) - if logger: - logger.debug('ChromaticRealGalaxy %d: Start ChromaticRealGalaxy constructor.', - use_index) + logger.debug('ChromaticRealGalaxy %d: Start ChromaticRealGalaxy constructor.', use_index) self.index = use_index # Read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors. imgs = [rgc.getGalImage(use_index) for rgc in real_galaxy_catalogs] - if logger: - logger.debug('ChromaticRealGalaxy %d: Got gal_image', use_index) + logger.debug('ChromaticRealGalaxy %d: Got gal_image', use_index) PSFs = [rgc.getPSF(use_index) for rgc in real_galaxy_catalogs] - if logger: - logger.debug('ChromaticRealGalaxy %d: Got psf', use_index) + logger.debug('ChromaticRealGalaxy %d: Got psf', use_index) bands = [rgc.getBandpass() for rgc in real_galaxy_catalogs] @@ -1070,8 +1054,7 @@ def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng= xi = _BaseCorrelatedNoise(self.rng, ii, noise_image.wcs) xi = xi.withVariance(var) xis.append(xi) - if logger: - logger.debug('ChromaticRealGalaxy %d: Got noise_image',use_index) + logger.debug('ChromaticRealGalaxy %d: Got noise_image',use_index) self.catalog_files = [rgc.getFileName() for rgc in real_galaxy_catalogs] self._initialize(imgs, bands, xis, PSFs, gsparams=gsparams, **kwargs) From 20acef6cadccf670f19756cd060f5bfcfcb1117e Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 16:30:44 -0400 Subject: [PATCH 92/96] A few more coverage additions (#755) --- tests/test_config_gsobject.py | 6 +++++- tests/test_convolve.py | 2 +- tests/test_utilities.py | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/test_config_gsobject.py b/tests/test_config_gsobject.py index ab2d3e6bf9d..46d9adb5697 100644 --- a/tests/test_config_gsobject.py +++ b/tests/test_config_gsobject.py @@ -1542,9 +1542,13 @@ def test_repeat(): config['obj_num'] = 4 gal2a = galsim.config.BuildGSObject(config, 'gal')[0] gsobject_compare(gal2a, gal2b) + + # Also check that the logger reports why it is using the current object config['obj_num'] = 5 - gal2a = galsim.config.BuildGSObject(config, 'gal')[0] + with CaptureLog() as cl: + gal2a = galsim.config.BuildGSObject(config, 'gal', logger=cl.logger)[0] gsobject_compare(gal2a, gal2b) + assert "repeat = 3, index = 5, use current object" in cl.output @timer diff --git a/tests/test_convolve.py b/tests/test_convolve.py index 0ab050ea36c..30f3d793f71 100644 --- a/tests/test_convolve.py +++ b/tests/test_convolve.py @@ -806,7 +806,7 @@ def test_convolve_noise(): deconv = galsim.Deconvolution(obj2) autoconv = galsim.AutoConvolution(obj2) autocorr = galsim.AutoCorrelation(obj2) - four = galsim.AutoCorrelation(obj2) + four = galsim.FourierSqrt(obj2) assert deconv.noise is None assert autoconv.noise is None assert autocorr.noise is None diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 269b70bb803..f73f1c17255 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -568,6 +568,13 @@ def test_deInterleaveImage(): assert_raises(ValueError, deInterleaveImage, image=img0, N=(2,7)) assert_raises(ValueError, deInterleaveImage, image=img0, N=(7,2)) + # It is legal to have the input image with wcs=None, but it emits a warning + img0.wcs = None + with assert_warns(galsim.GalSimWarning): + deInterleaveImage(img0, N=2) + # Unless suppress_warnings is True + deInterleaveImage(img0, N=2, suppress_warnings=True) + @timer def test_interleaveImages(): @@ -778,6 +785,13 @@ def test_interleaveImages(): assert_raises(TypeError, interleaveImages, im_list, N=n, offsets=offset_list[0]) assert_raises(TypeError, interleaveImages, im_list, N=n, offsets=range(n*n)) + # It is legal to have the input images with wcs=None, but it emits a warning + for im in im_list: + im.wcs = None + with assert_warns(galsim.GalSimWarning): + interleaveImages(im_list, N=n, offsets=offset_list) + # Unless suppress_warnings is True + interleaveImages(im_list, N=n, offsets=offset_list, suppress_warnings=True) @timer From 3f2b197cde1ddc408f1ddd729525224bb2e82632 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 21:00:15 -0400 Subject: [PATCH 93/96] Remove unnecessary decode lines (#755) --- galsim/fits.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/galsim/fits.py b/galsim/fits.py index 16c3994b954..c5d142e09c9 100644 --- a/galsim/fits.py +++ b/galsim/fits.py @@ -1143,15 +1143,6 @@ def __iter__(self): return self.header.__iter__() def __setitem__(self, key, value): - # pyfits doesn't like getting bytes in python 3, so decode if appropriate - try: - key = str(key.decode()) - except AttributeError: - pass - try: - value = str(value.decode()) - except AttributeError: - pass self._tag = None self.header[key] = value From 0c804e2215d35eaff52535cf903d42f96f450f56 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 21:00:55 -0400 Subject: [PATCH 94/96] Just set truediv = div, rather then defining it as a new function (#755) --- galsim/bandpass.py | 3 +-- galsim/chromatic.py | 20 +++++++++----------- galsim/correlatednoise.py | 3 +-- galsim/position.py | 3 +-- galsim/sed.py | 3 +-- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/galsim/bandpass.py b/galsim/bandpass.py index 6c1ca449039..f3a7d4dfea1 100644 --- a/galsim/bandpass.py +++ b/galsim/bandpass.py @@ -316,8 +316,7 @@ def __div__(self, other): return Bandpass(tp, wave_type, self.blue_limit, self.red_limit, _wave_list=self.wave_list) - def __truediv__(self, other): - return self.__div__(other) + __truediv__ = __div__ def __call__(self, wave): """ Return dimensionless throughput of bandpass at given wavelength in nanometers. diff --git a/galsim/chromatic.py b/galsim/chromatic.py index c716c0736f2..6680bdb66fa 100644 --- a/galsim/chromatic.py +++ b/galsim/chromatic.py @@ -533,6 +533,7 @@ def evaluateAtWavelength(self, wave): "Subclasses of ChromaticObject must override evaluateAtWavelength()") return self._obj.evaluateAtWavelength(wave) + # Make op* and op*= work to adjust the flux of the object def __mul__(self, flux_ratio): """Scale the flux of the object by the given flux ratio, which may be an SED, a float, or a univariate callable function (of wavelength in nanometers) that returns a float. @@ -556,6 +557,14 @@ def __mul__(self, flux_ratio): """ return self.withScaledFlux(flux_ratio) + __rmul__ = __mul__ + + # Likewise for op/ and op/= + def __div__(self, other): + return self.__mul__(1./other) + + __truediv__ = __div__ + def withScaledFlux(self, flux_ratio): """Multiply the flux of the object by `flux_ratio` @@ -704,17 +713,6 @@ def __add__(self, other): def __sub__(self, other): return ChromaticSum(self, -other) - # Make op* and op*= work to adjust the flux of the object - def __rmul__(self, other): - return self.__mul__(other) - - # Likewise for op/ and op/= - def __div__(self, other): - return self.__mul__(1./other) - - def __truediv__(self, other): - return self.__div__(other) - def __neg__(self): return -1. * self diff --git a/galsim/correlatednoise.py b/galsim/correlatednoise.py index d305a316155..3222a58a992 100644 --- a/galsim/correlatednoise.py +++ b/galsim/correlatednoise.py @@ -128,8 +128,7 @@ def __mul__(self, variance_ratio): return self.withScaledVariance(variance_ratio) def __div__(self, variance_ratio): return self.withScaledVariance(1./variance_ratio) - def __truediv__(self, variance_ratio): - return self.withScaledVariance(1./variance_ratio) + __truediv__ = __div__ def copy(self, rng=None): """Returns a copy of the correlated noise model. diff --git a/galsim/position.py b/galsim/position.py index 9c17952c08f..e45e2e8983c 100644 --- a/galsim/position.py +++ b/galsim/position.py @@ -116,8 +116,7 @@ def __div__(self, other): self._check_scalar(other, 'divide') return self.__class__(self.x / other, self.y / other) - def __truediv__(self, other): - return self.__div__(other) + __truediv__ = __div__ def __neg__(self): return self.__class__(-self.x, -self.y) diff --git a/galsim/sed.py b/galsim/sed.py index 9fa3a615f62..09b4e7afb0e 100644 --- a/galsim/sed.py +++ b/galsim/sed.py @@ -598,8 +598,7 @@ def __div__(self, other): _wave_list=self.wave_list, _blue_limit=self.blue_limit, _red_limit=self.red_limit) - def __truediv__(self, other): - return self.__div__(other) + __truediv__ = __div__ def __add__(self, other): # Add together two SEDs, with the following caveats: From c39435e0da7d6faecf984e9e6b51b6949c3d7a7d Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 21:52:36 -0400 Subject: [PATCH 95/96] Add another test of no photons warning (#755) --- tests/test_draw.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_draw.py b/tests/test_draw.py index 901047254c2..87e26261a83 100644 --- a/tests/test_draw.py +++ b/tests/test_draw.py @@ -1068,6 +1068,11 @@ def test_shoot(): psf = galsim.Gaussian(sigma=3) psf.drawImage(method='phot') + # Also if flux << 1, n_photons will end up 0. + with assert_warns(galsim.GalSimWarning): + psf = galsim.Gaussian(sigma=3, flux=1.e-5) + psf.drawImage(method='phot') + @timer def test_drawImage_area_exptime(): From 1523340eee71ae3a9ac17e5a6532c0cffaeb1175 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Thu, 17 May 2018 23:24:20 -0400 Subject: [PATCH 96/96] Make sure test of RandomGaussian with min/max has to loop at least once. (#755) --- galsim/config/value_random.py | 18 +++++++------- tests/test_config_value.py | 45 ++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/galsim/config/value_random.py b/galsim/config/value_random.py index 71f34f6cf99..d8356525a2c 100644 --- a/galsim/config/value_random.py +++ b/galsim/config/value_random.py @@ -96,25 +96,25 @@ def _GenerateFromRandomGaussian(config, base, value_type): do_abs = False do_neg = False - if min == mean: + if (min >= mean) and (max > mean): do_abs = True - max -= mean - min = -max - elif max == mean: + lo = min - mean + hi = max - mean + elif (min < mean) and (max <= mean): do_abs = True do_neg = True - min -= mean - max = -min + hi = mean - min + lo = mean - max else: - min -= mean - max -= mean + lo = min - mean + hi = max - mean # Emulate a do-while loop import math while True: val = gd() if do_abs: val = math.fabs(val) - if val >= min and val <= max: break + if val >= lo and val <= hi: break if do_neg: val = -val val += mean else: diff --git a/tests/test_config_value.py b/tests/test_config_value.py index 75434f9526b..8f3144c42f0 100644 --- a/tests/test_config_value.py +++ b/tests/test_config_value.py @@ -59,12 +59,16 @@ def test_float_value(): 'ran1' : { 'type' : 'Random', 'min' : 0.5, 'max' : 3 }, 'ran2' : { 'type' : 'Random', 'min' : -5, 'max' : 0 }, 'gauss1' : { 'type' : 'RandomGaussian', 'sigma' : 1 }, + 'gauss1b' : { 'type' : 'RandomGaussian', 'sigma' : 1 }, 'gauss2' : { 'type' : 'RandomGaussian', 'sigma' : 3, 'mean' : 4 }, 'gauss3' : { 'type' : 'RandomGaussian', 'sigma' : 1.5, 'min' : -2, 'max' : 2 }, 'gauss4' : { 'type' : 'RandomGaussian', 'sigma' : 0.5, 'min' : 0, 'max' : 0.8 }, 'gauss5' : { 'type' : 'RandomGaussian', 'sigma' : 0.3, 'mean' : 0.5, 'min' : 0, 'max' : 0.5 }, - 'gauss6' : { 'type' : 'RandomGaussian', 'sigma' : 1 }, + 'gauss6' : { 'type' : 'RandomGaussian', + 'sigma' : 0.8, 'mean' : 0.3, 'min' : 2. }, + 'gauss7' : { 'type' : 'RandomGaussian', + 'sigma' : 1.3, 'mean' : 0.3, 'min' : -2., 'max' : 0. }, 'dist1' : { 'type' : 'RandomDistribution', 'function' : 'config_input/distribution.txt', 'interpolant' : 'linear' }, 'dist2' : { 'type' : 'RandomDistribution', @@ -212,6 +216,22 @@ def test_float_value(): gd_val = 1-gd_val np.testing.assert_almost_equal(gauss5, gd_val) + gauss6 = galsim.config.ParseValue(config,'gauss6',config, float)[0] + gd = galsim.GaussianDeviate(rng,mean=0.,sigma=0.8) + gd_val = abs(gd()) + while gd_val < 1.7: + gd_val = abs(gd()) + gd_val += 0.3 + np.testing.assert_almost_equal(gauss6, gd_val) + + gauss7 = galsim.config.ParseValue(config,'gauss7',config, float)[0] + gd = galsim.GaussianDeviate(rng,mean=0.,sigma=1.3) + gd_val = abs(gd()) + while gd_val < 0.3 or gd_val > 2.3: + gd_val = abs(gd()) + gd_val = -gd_val + 0.3 + np.testing.assert_almost_equal(gauss7, gd_val) + # Test values generated from a distribution in a file dd=galsim.DistDeviate(rng,function='config_input/distribution.txt',interpolant='linear') for k in range(6): @@ -372,7 +392,8 @@ def test_float_value(): # Test PowerSpectrumMagnification ps = galsim.PowerSpectrum(e_power_function='np.exp(-k**0.2)') galsim.config.RemoveCurrent(config) - config['rng'] = rng.duplicate() # reset them back to be in sync + rng = galsim.BaseDeviate(31415) # reset this so changes to tests above don't mess this up. + config['rng'] = rng.duplicate() ps.buildGrid(grid_spacing=10, ngrid=20, interpolant='linear', rng=rng) print("ps mag = ",ps.getMagnification((0.1,0.2))) galsim.config.SetupInputsForImage(config, None) @@ -380,24 +401,26 @@ def test_float_value(): np.testing.assert_almost_equal(ps1, ps.getMagnification((0.1,0.2))) # Beef up the amplitude to get strong lensing. - ps = galsim.PowerSpectrum(e_power_function='500 * np.exp(-k**0.2)') + ps = galsim.PowerSpectrum(e_power_function='100 * np.exp(-k**0.2)') ps.buildGrid(grid_spacing=10, ngrid=20, interpolant='linear', rng=rng) print("strong lensing mag = ",ps.getMagnification((0.1,0.2))) config = galsim.config.CleanConfig(config) - config['input']['power_spectrum']['e_power_function'] = '500 * np.exp(-k**0.2)' + config['input']['power_spectrum']['e_power_function'] = '100 * np.exp(-k**0.2)' with CaptureLog() as cl: galsim.config.SetupInputsForImage(config, logger=cl.logger) ps2a = galsim.config.ParseValue(config,'ps',config, float)[0] - assert 'PowerSpectrum mu = -3.643856 means strong lensing. Using mu=25.000000' in cl.output + print(cl.output) + assert 'PowerSpectrum mu = -2.778083 means strong lensing. Using mu=25.000000' in cl.output np.testing.assert_almost_equal(ps2a, 25.) - galsim.config.RemoveCurrent(config) # Need a different point that happens to have strong lensing, since the PS realization changed. - config['world_pos'] = galsim.PositionD(3.1,24.2) + config['world_pos'] = galsim.PositionD(5,75) + galsim.config.RemoveCurrent(config) with CaptureLog() as cl: - galsim.config.SetupInputsForImage(config, cl.logger) + galsim.config.SetupInputsForImage(config, logger=cl.logger) ps2b = galsim.config.ParseValue(config, 'ps', config, float)[0] - assert "Warning: PowerSpectrum mu = 29.287659 means strong lensing." in cl.output + print(cl.output) + assert "PowerSpectrum mu = 26.949446 means strong lensing. Using mu=25.000000" in cl.output np.testing.assert_almost_equal(ps2b, 25.) # Or set a different maximum @@ -405,7 +428,7 @@ def test_float_value(): config['ps']['max_mu'] = 30. del config['ps']['_get'] ps3 = galsim.config.ParseValue(config,'ps',config, float)[0] - np.testing.assert_almost_equal(ps3, 29.28765853271335) + np.testing.assert_almost_equal(ps3, 26.949445807939387) # Negative max_mu is invalid. galsim.config.RemoveCurrent(config) @@ -457,7 +480,7 @@ def test_float_value(): # Error if given the wrong type. Should be float, not np.float16. with assert_raises(galsim.GalSimConfigError): - galsim.config.ParseValue(config,'gauss6',config, np.float16) + galsim.config.ParseValue(config,'gauss1b',config, np.float16) # Different path to (different) error if already processed into a _gen_fn. galsim.config.ParseValue(config,'gauss1',config, float) with assert_raises(galsim.GalSimConfigError):