From 414ab6bd95af2f9b02b2593c6ef73401f1923772 Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Mon, 13 May 2024 12:49:35 -0400 Subject: [PATCH 1/8] update ruff to ignore init imports --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f25b9e24..82ef6533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,6 +190,7 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +ignore-init-module-imports = true [tool.ruff.format] # Like Black, use double quotes for strings. From fc1decef0268481f1eee1e328f4945862aaf4c7e Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 10:01:47 -0400 Subject: [PATCH 2/8] manual lint changes --- dev_utils/field_dependence/basis.py | 6 +-- dev_utils/package_filters.py | 6 ++- docs/conf.py | 2 +- webbpsf/detectors.py | 3 +- webbpsf/gridded_library.py | 9 ++-- webbpsf/jupyter_gui.py | 4 +- webbpsf/mast_wss.py | 10 ++--- webbpsf/opds.py | 11 ++--- webbpsf/optical_budget.py | 12 ++--- webbpsf/optics.py | 20 ++++----- webbpsf/roman.py | 7 +-- webbpsf/tests/test_errorhandling.py | 7 ++- webbpsf/tests/test_fgs.py | 23 ++++++---- webbpsf/tests/test_miri.py | 44 +++++++++++------- webbpsf/tests/test_nircam.py | 69 ++++++++++++++++------------- webbpsf/tests/test_niriss.py | 23 ++++++---- webbpsf/tests/test_nirspec.py | 21 +++++---- webbpsf/tests/test_opds.py | 9 ++-- webbpsf/tests/test_roman.py | 21 +++------ webbpsf/tests/test_utils.py | 17 +++---- webbpsf/tests/test_webbpsf.py | 15 +++---- webbpsf/tests/validate_webbpsf.py | 31 ++++++------- webbpsf/trending.py | 40 ++++++++--------- webbpsf/utils.py | 9 ++-- webbpsf/webbpsf_core.py | 38 ++++++++-------- 25 files changed, 233 insertions(+), 224 deletions(-) diff --git a/dev_utils/field_dependence/basis.py b/dev_utils/field_dependence/basis.py index 0ad3ed14..87e47cb0 100644 --- a/dev_utils/field_dependence/basis.py +++ b/dev_utils/field_dependence/basis.py @@ -18,22 +18,22 @@ # Disable Pint's old fallback behavior (must come before importing Pint) import os +import copy os.environ['PINT_ARRAY_PROTOCOL_FALLBACK'] = '0' -import pint +import pint # noqa units = pint.UnitRegistry() Q_ = units.Quantity # Silence NEP 18 warning -import warnings +import warnings # noqa with warnings.catch_warnings(): warnings.simplefilter('ignore') Q_([]) -import copy def embed(n, m): diff --git a/dev_utils/package_filters.py b/dev_utils/package_filters.py index 69a74ba4..1f89f86f 100644 --- a/dev_utils/package_filters.py +++ b/dev_utils/package_filters.py @@ -4,6 +4,8 @@ import stsynphot +from synphot.exceptions import SynphotError + WebbPSF_basepath = os.getenv( 'WEBBPSF_PATH', default=os.path.dirname(os.path.dirname(os.path.abspath(webbpsf.__file__))) + os.sep + 'data' ) @@ -21,11 +23,11 @@ def norm_one_filter(instrument, filter_, clobber=False): t.add_keyword('TELESCOP', 'JWST') t.add_keyword('INSTRUME', instrument) t.add_keyword('FILTER', filter_) - t.add_keyword('SOURCE', f'{_SYNPHOT_PKG}, normalized to peak=1') + t.add_keyword('SOURCE', f'{_SYNPHOT_PKG}, normalized to peak=1') # TODO - NOT DEFINED _SYNPHOT_PKG t.write('%s/%s/filters/%s_throughput.fits' % (WebbPSF_basepath, instrument, filter_)) print('Wrote throughput.fits for %s %s' % (instrument, filter_)) - except: + except SynphotError: print('Error for %s %s' % (instrument, filter_)) diff --git a/docs/conf.py b/docs/conf.py index 33973b0b..cf1a80d0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -118,7 +118,7 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" -intersphinx_mapping.update( +intersphinx_mapping.update( # noqa - defined in star import { "poppy": ("http://poppy-optics.readthedocs.io/", None), } diff --git a/webbpsf/detectors.py b/webbpsf/detectors.py index b687deaa..40f0ad29 100644 --- a/webbpsf/detectors.py +++ b/webbpsf/detectors.py @@ -74,7 +74,6 @@ def get_detector_ipc_model(inst, header): elif inst == 'MIRI': webbpsf.webbpsf_core._log.info('Detector IPC: MIRI') - a = webbpsf.constants.INSTRUMENT_IPC_DEFAULT_KERNEL_PARAMETERS[inst] alpha = webbpsf.constants.INSTRUMENT_IPC_DEFAULT_KERNEL_PARAMETERS[inst][0] beta = webbpsf.constants.INSTRUMENT_IPC_DEFAULT_KERNEL_PARAMETERS[inst][1] c = webbpsf.constants.INSTRUMENT_IPC_DEFAULT_KERNEL_PARAMETERS[inst][2] # real observation noise adjustment @@ -107,7 +106,7 @@ def get_detector_ipc_model(inst, header): nchannel = int(yposition) // 512 try: flag = maskimage[nchannel, int(xposition)] - except: + except IndexError: # This marks the pixel as non-void by default if the maskimage is not # read in properly flag = 0 diff --git a/webbpsf/gridded_library.py b/webbpsf/gridded_library.py index a75f9018..df83d62c 100644 --- a/webbpsf/gridded_library.py +++ b/webbpsf/gridded_library.py @@ -155,7 +155,7 @@ def __init__( from photutils.psf import GriddedPSFModel except ImportError: try: - from photutils import GriddedPSFModel + from photutils import GriddedPSFModel # noqa except ImportError: raise ImportError('This method requires photutils >= 0.6') @@ -232,7 +232,7 @@ def _set_detectors(self, filt, detectors): det = CreatePSFLibrary.nrca_short_detectors elif self.instr == 'NIRCam' and filt in CreatePSFLibrary.nrca_long_filters: det = CreatePSFLibrary.nrca_long_detectors - elif type(detectors) is str: + elif isinstance(detectors, str): det = detectors.split() else: raise TypeError('Method of setting detectors is not valid') @@ -583,7 +583,10 @@ def display_psf_grid(grid, zoom_in=True, figsize=(14, 12), scale_range=1e-4, dif import matplotlib import matplotlib.pyplot as plt - tuple_to_int = lambda t: (int(t[0]), int(t[1])) + def tuple_to_int(t): + if isinstance(t, tuple): + return (int(t[0]), int(t[1])) + def show_grid_helper(grid, data, title='Grid of PSFs', vmax=0, vmin=0, scale='log'): npsfs = grid.data.shape[0] diff --git a/webbpsf/jupyter_gui.py b/webbpsf/jupyter_gui.py index 9c55699d..0609858c 100644 --- a/webbpsf/jupyter_gui.py +++ b/webbpsf/jupyter_gui.py @@ -36,7 +36,7 @@ def show_notebook_interface_wfi(instrument): from matplotlib import pyplot as plt try: - import synphot + import synphot # noqa TODO verify synphot is indeeded needed except ImportError: raise ImportError('For now, synphot must be installed to use the notebook interface') @@ -224,7 +224,7 @@ def show_notebook_interface_jwst(instrument): instrument = instrument(instrument) try: - import synphot + import synphot # noqa TODO verify synphot is indeeded needed except ImportError: raise ImportError('For now, synphot must be installed to use the notebook interface') diff --git a/webbpsf/mast_wss.py b/webbpsf/mast_wss.py index 482632fc..0c93677d 100644 --- a/webbpsf/mast_wss.py +++ b/webbpsf/mast_wss.py @@ -222,8 +222,8 @@ def get_opd_at_time(date, choice='closest', verbose=False, output_path=None): elif choice == 'average': if verbose: print(f'User requested calculating OPD time averaged around {date}') - fn_pre = mast_retrieve_opd(pre_opd_fn, output_path=output_path) - fn_post = mast_retrieve_opd(post_opd_fn, output_path=output_path) + mast_retrieve_opd(pre_opd_fn, output_path=output_path) # TODO - define pre_opd_fn now or skip? + mast_retrieve_opd(post_opd_fn, output_path=output_path) raise NotImplementedError('Not yet implemented') elif choice == 'closest': closest_fn, closest_dt = ( @@ -424,15 +424,11 @@ def add_columns_to_track_corrections(opdtable): """ # - dates = astropy.time.Time(opdtable['date'], format='isot') pre_or_post = [] for row in opdtable: pre_or_post.append(infer_pre_or_post_correction(row)) - where_pre = ['pre' in a for a in pre_or_post] - where_post = ['post' in a for a in pre_or_post] - # Add column for is this WFS measurement made immediately after a correction opdtable['wfs_measurement_type'] = pre_or_post opdtable['is_post_correction'] = ['post' in a for a in pre_or_post] @@ -698,7 +694,7 @@ def download_wfsc_images(program=None, obs=None, verbose=False, **kwargs): filetable = query_wfsc_images_latest(**kwargs) else: if verbose: - print(f'Querying WFSC images from program {prog}, observation {obs}') + print(f'Querying WFSC images from program {program}, observation {obs}') filetable = query_wfsc_images_by_program(program, obs, **kwargs) # If we found > 0 available files for that visit, then we're done searching and can go on to download diff --git a/webbpsf/opds.py b/webbpsf/opds.py index 7bfa93db..20c76d69 100644 --- a/webbpsf/opds.py +++ b/webbpsf/opds.py @@ -261,7 +261,7 @@ def powerspectrum(self, max_cycles=50, sampling=5, vmax=100, iterate=False): """ - # import SFT + import SFT def tvcircle(radius=1, xcen=0, ycen=0, center=None, **kwargs): """ @@ -1362,8 +1362,6 @@ def zero(self, zero_original=False): _log.info('Set OPD to zero WFE!') def print_state(self): - keys = self.state.keys() - print('Segment poses in Control coordinates: (microns for decenter & piston, microradians for tilts and clocking):') print(' \t %10s %10s %10s %10s %10s %10s' % tuple(self._control_modes)) for i, segment in enumerate(self.segnames[0:18]): @@ -1492,7 +1490,7 @@ def _apply_hexikes_to_seg(self, segment, hexike_coeffs, debug=False): del self.meta[f'S{iseg:02d}PISTN'] del self.meta[f'S{iseg:02d}XTILT'] del self.meta[f'S{iseg:02d}YTILT'] - except: + except KeyError: pass for i in range(len(hexike_coeffs)): @@ -2979,7 +2977,7 @@ def random_unstack(ote, radius=1, verbose=False): def test_OPDbender(): plt.figure(1) - tel = OPDbender() + tel = OPDbender() # TODO remove this whole test? tel.displace('A1', 1, 0, 0, display=False) tel.displace('A2', 0, 1, 0, display=False) tel.displace('A3', 0, 0, 0.03, display=False) @@ -3003,7 +3001,7 @@ def test_OPDbender(): def test2_OPDbender(filename='OPD_RevV_nircam_132.fits'): - orig = OPDbender(filename) + orig = OPDbender(filename) # TODO remove this whole test? plot_kwargs = {'colorbar_orientation': 'horizontal', 'clear': False} @@ -3480,7 +3478,6 @@ def get_coarse_blur_parameters( pcsmodel = astropy.table.Table.read(os.path.join(__location__, 'otelm', f'coarse_track{case}_sim_pointing.fits')) wt = (t0 < pcsmodel['time']) & (pcsmodel['time'] < t0 + duration) - ns = wt.sum() # Extract coordinates for the requested time period coords = np.zeros((2, wt.sum()), float) diff --git a/webbpsf/optical_budget.py b/webbpsf/optical_budget.py index 37e84481..af43b948 100644 --- a/webbpsf/optical_budget.py +++ b/webbpsf/optical_budget.py @@ -186,7 +186,9 @@ def visualize_wfe_budget(inst, slew_delta_time=14 * u.day, slew_case='EOL', ptt_ Be more verbose """ - vprint = lambda x: print(x) if verbose else None + def vprint(x): + if verbose: + print(x) loc = f'{inst.detector} at {inst.detector_position}' @@ -344,7 +346,7 @@ def visualize_wfe_budget(inst, slew_delta_time=14 * u.day, slew_case='EOL', ptt_ ax.text( 0.96, 0.92, - f'45$^\circ$, {slew_delta_time}', + f'45$^\circ$, {slew_delta_time}', # noqa transform=ax.transAxes, horizontalalignment='right', ) @@ -359,7 +361,7 @@ def visualize_wfe_budget(inst, slew_delta_time=14 * u.day, slew_case='EOL', ptt_ annotate_budget='Image motion (as equiv. WFE)', instname=inst.name, ) - ax.text(0.04, 0.92, f'LOS jitter: {jitter}, 1$\sigma$/axis', transform=ax.transAxes) + ax.text(0.04, 0.92, f'LOS jitter: {jitter}, 1$\sigma$/axis', transform=ax.transAxes) # noqa sigma not escape char # ISIM and SI show_opd( @@ -425,8 +427,8 @@ def bounds(axis): ) # Annotate first column to show WFE summation - between_row1_row2 = (bounds(axes[1, 0])[0, 1] + bounds(axes[2, 0])[1, 1]) / 2 - between_row2_row3 = (bounds(axes[2, 0])[0, 1] + bounds(axes[3, 0])[1, 1]) / 2 + between_row1_row2 = (bounds(axes[1, 0])[0, 1] + bounds(axes[2, 0])[1, 1]) / 2 # TODO - Unused values okay to delete? + between_row2_row3 = (bounds(axes[2, 0])[0, 1] + bounds(axes[3, 0])[1, 1]) / 2 # TODO - Unused values okay to delete? meanx_col0 = bounds(axes[0, 0])[:, 0].mean() for irow, symbol in enumerate(['=', '+', '+']): diff --git a/webbpsf/optics.py b/webbpsf/optics.py index 9916c635..bb947df4 100644 --- a/webbpsf/optics.py +++ b/webbpsf/optics.py @@ -233,8 +233,8 @@ def get_transmission(self, wave): The walls separating adjacent shutters are 0.06 arcsec wide. """ - msa_width = 0.2 - msa_height = 0.45 + msa_width = 0.2 # TODO - Unused values okay to delete? + msa_height = 0.45 # TODO - Unused values okay to delete? msa_wall = 0.06 msa_x_pitch = 0.26 msa_y_pitch = 0.51 @@ -388,7 +388,7 @@ def __init__( if which == 'LLNL': raise NotImplementedError('Rotated field mask for LLNL grism not yet implemented!') elif which == 'Bach': - transmission = os.path.join(utils.get_webbpsf_data_path(), 'NIRISS/optics/MASKGR700XD.fits.gz') + transmission = os.path.join(utils.get_webbpsf_data_path(), 'NIRISS/optics/MASKGR700XD.fits.gz') # TODO - Unused value, delete entire statement? else: raise NotImplementedError('Unknown grating name:' + which) @@ -447,7 +447,7 @@ def __init__( self.pupil_demagnification = 6.6 / 0.040 # about 165 # perform an initial population of the OPD array for display etc. - tmp = self.get_phasor(poppy.Wavefront(2e-6)) + self.get_phasor(poppy.Wavefront(2e-6)) def get_opd(self, wave): """Make an OPD array corresponding to the cylindrical weak lens @@ -513,7 +513,7 @@ def get_opd(self, wave): # now compute the spatially dependent sag of the cylinder, as projected onto the primary # what is the pupil scale at the *reimaged pupil* of the grism? - pupil_scale_m_per_pix = 38.0255e-6 # Based on UdeM info in wfe_cylindricallens.pro + pupil_scale_m_per_pix = 38.0255e-6 # Based on UdeM info in wfe_cylindricallens.pro # TODO - unused, can be deleted or just commented out? # sag = np.sqrt(self.cylinder_radius**2 - (x*self.amplitude_header['PUPLSCAL']/self.pupil_demagnification)**2) - self.cylinder_radius sag = np.sqrt(self.cylinder_radius**2 - (x / self.pupil_demagnification) ** 2) - self.cylinder_radius # sag = self.cylinder_radius - np.sqrt(self.cylinder_radius**2 - (x * pupil_scale_m_per_pix )**2 ) @@ -527,7 +527,7 @@ def get_opd(self, wave): # no OPD in opaque regions (makes no difference in propagation but improves display) if self._transmission.shape != sag.shape: - tmp = self.get_transmission() # Update the ._transmission attribute + self.get_transmission() # Update the ._transmission attribute sag[self._transmission == 0] = 0 wnz = np.where(self._transmission != 0) # use this just for display of the log messages: _log.debug( @@ -549,7 +549,7 @@ def get_opd(self, wave): def get_transmission(self, wave): """Make array for the pupil obscuration appropriate to the grism""" - if isinstance(wave, poppy.Wavefront): + if isinstance(wave, poppy.Wavefront): # TODO - Wavelength isn't used, safe to delete in this function? wavelength = wave.wavelength else: wave = poppy.Wavefront(wavelength=float(wave)) @@ -776,7 +776,7 @@ def __init__( _log.debug( 'Set bar offset to {} based on requested filter {} on {}.'.format(bar_offset, auto_offset, self.name) ) - except: + except (KeyError, IndexError): raise ValueError( 'Filter {} does not have a defined nominal offset position along {}'.format(auto_offset, self.name) ) @@ -841,7 +841,7 @@ def get_transmission(self, wave): if poppy.accel_math._USE_NUMEXPR: import numexpr as ne - jn1 = scipy.special.j1(sigmar) + jn1 = scipy.special.j1(sigmar) # noqa TODO - this looks like a bug, should jn1 be formatted in the following line? self.transmission = ne.evaluate('(1 - (2 * jn1 / sigmar) ** 2)') else: self.transmission = 1 - (2 * scipy.special.j1(sigmar) / sigmar) ** 2 @@ -986,7 +986,7 @@ def get_transmission(self, wave): if not np.isfinite(self.transmission.sum()): # stop() _log.warn('There are NaNs in the BLC mask - correcting to zero. (DEBUG LATER?)') - self.transmission[np.where(np.isfinite(self.transmission) == False)] = 0 + self.transmission[np.where(np.isfinite(self.transmission) is False)] = 0 return self.transmission def display(self, annotate=False, annotate_color='cyan', annotate_text_color=None, grid_size=20, *args, **kwargs): diff --git a/webbpsf/roman.py b/webbpsf/roman.py index df8f6fe4..8e1ecaaf 100644 --- a/webbpsf/roman.py +++ b/webbpsf/roman.py @@ -10,6 +10,7 @@ import logging import os.path +import pprint import astropy.units as u import numpy as np @@ -20,7 +21,6 @@ from . import distortion, utils, webbpsf_core _log = logging.getLogger('webbpsf') -import pprint GRISM_FILTERS = ('GRISM0', 'GRISM1') PRISM_FILTERS = ('PRISM',) @@ -219,7 +219,6 @@ def build_detector_from_table(number, zernike_table): Zernikes Z1-Z22 at various wavelengths and field points""" single_detector_info = zernike_table[zernike_table['sca'] == number] field_points = set(single_detector_info['field_point']) - interpolators = {} detector = FieldDependentAberration( 4096, 4096, radius=RomanInstrument.PUPIL_RADIUS, name='Field Dependent Aberration (SCA{:02})'.format(number) ) @@ -310,7 +309,7 @@ def calc_psf( self.options['crop_psf'] = crop_psf # add_distortion keyword is not implemented for RomanCoronagraph Class - if self.name == 'RomanCoronagraph' and add_distortion == True: + if self.name == 'RomanCoronagraph' and add_distortion is True: self.options['add_distortion'] = False self.options['crop_psf'] = False _log.info( @@ -395,7 +394,6 @@ def _calc_psf_format_output(self, result, options): """ # Pull values from options dictionary add_distortion = options.get('add_distortion', True) - crop_psf = options.get('crop_psf', True) # Add distortion if set in calc_psf if add_distortion: _log.debug('Adding PSF distortion(s)') @@ -1204,7 +1202,6 @@ def _get_aberrations(self): def _get_fits_header(self, result, options): """Populate FITS Header keywords""" super()._get_fits_header(result, options) - pupil_hdr = fits.getheader(self.pupil) apodizer_hdr = fits.getheader(self._apodizer_fname) fpm_hdr = fits.getheader(self._fpm_fname) lyotstop_hdr = fits.getheader(self._lyotstop_fname) diff --git a/webbpsf/tests/test_errorhandling.py b/webbpsf/tests/test_errorhandling.py index 006fb6fe..e45d06ce 100644 --- a/webbpsf/tests/test_errorhandling.py +++ b/webbpsf/tests/test_errorhandling.py @@ -5,13 +5,12 @@ import os import os.path -_log = logging.getLogger('test_webbpsf') -_log.addHandler(logging.NullHandler()) - import pytest from .. import conf, utils, webbpsf_core +_log = logging.getLogger('test_webbpsf') +_log.addHandler(logging.NullHandler()) def _exception_message_starts_with(excinfo, message_body): return excinfo.value.args[0].startswith(message_body) @@ -24,7 +23,7 @@ def test_calc_psf_catch_incompatible_oversampling(): nc.filter = 'F212N' with pytest.raises(ValueError) as excinfo: - psf = nc.calc_psf(oversample=2, detector_oversample=10, fft_oversample=4) + nc.calc_psf(oversample=2, detector_oversample=10, fft_oversample=4) assert _exception_message_starts_with(excinfo, 'You cannot specify') diff --git a/webbpsf/tests/test_fgs.py b/webbpsf/tests/test_fgs.py index a3f42a75..03d370c0 100644 --- a/webbpsf/tests/test_fgs.py +++ b/webbpsf/tests/test_fgs.py @@ -1,19 +1,26 @@ import logging +from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test + _log = logging.getLogger('test_webbpsf') _log.addHandler(logging.NullHandler()) +# ------------------ FGS Tests ---------------------------- +def test_fgs(): + return generic_output_test('FGS') -# ------------------ FGS Tests ---------------------------- -from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test +def test_fgs_source_offset_00(): + return do_test_source_offset('FGS', theta=0.0, monochromatic=2.5e-6) + + +def test_fgs_source_offset_45(): + return do_test_source_offset('FGS', theta=45.0, monochromatic=2.5e-6) -test_fgs = lambda: generic_output_test('FGS') -test_fgs_source_offset_00 = lambda: do_test_source_offset('FGS', theta=0.0, monochromatic=2.5e-6) -test_fgs_source_offset_45 = lambda: do_test_source_offset('FGS', theta=45.0, monochromatic=2.5e-6) -test_fgs_set_siaf = lambda: do_test_set_position_from_siaf( - 'FGS', ['FGS1_FP1MIMF', 'FGS2_SUB128CNTR', 'FGS1_SUB128LL', 'FGS2_SUB32DIAG'] -) +def test_fgs_set_siaf(): + return do_test_set_position_from_siaf( + 'FGS', ['FGS1_FP1MIMF', 'FGS2_SUB128CNTR', 'FGS1_SUB128LL', 'FGS2_SUB32DIAG'] + ) diff --git a/webbpsf/tests/test_miri.py b/webbpsf/tests/test_miri.py index 0cb78997..347ab1e9 100644 --- a/webbpsf/tests/test_miri.py +++ b/webbpsf/tests/test_miri.py @@ -1,31 +1,41 @@ import logging import os + import astropy.units as u -import pysiaf import numpy as np +import pysiaf + +from .. import webbpsf_core +from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test _log = logging.getLogger('test_webbpsf') _log.addHandler(logging.NullHandler()) -from .. import webbpsf_core # ------------------ MIRI Tests ---------------------------- -from .test_webbpsf import generic_output_test, do_test_source_offset, do_test_set_position_from_siaf - -test_miri = lambda: generic_output_test('MIRI') -test_miri_source_offset_00 = lambda: do_test_source_offset('MIRI', theta=0.0, monochromatic=8e-6) -test_miri_source_offset_45 = lambda: do_test_source_offset('MIRI', theta=45.0, monochromatic=8e-6) - -test_miri_set_siaf = lambda: do_test_set_position_from_siaf( - 'MIRI', - [ - 'MIRIM_SUB128', - 'MIRIM_FP1MIMF', - 'MIRIM_BRIGHTSKY', - 'MIRIM_TASLITLESSPRISM', - ], -) +def test_miri(): + return generic_output_test('MIRI') + + +def test_miri_source_offset_00(): + return do_test_source_offset('MIRI', theta=0.0, monochromatic=8e-6) + + +def test_miri_source_offset_45(): + return do_test_source_offset('MIRI', theta=45.0, monochromatic=8e-6) + + +def test_miri_set_siaf(): + return do_test_set_position_from_siaf( + 'MIRI', + [ + 'MIRIM_SUB128', + 'MIRIM_FP1MIMF', + 'MIRIM_BRIGHTSKY', + 'MIRIM_TASLITLESSPRISM', + ], + ) def do_test_miri_fqpm( diff --git a/webbpsf/tests/test_nircam.py b/webbpsf/tests/test_nircam.py index 80edd94e..2c7d1ba7 100644 --- a/webbpsf/tests/test_nircam.py +++ b/webbpsf/tests/test_nircam.py @@ -5,30 +5,43 @@ import astropy.io.fits as fits import matplotlib.pyplot as plt import numpy as np - -_log = logging.getLogger('test_webbpsf') -_log.addHandler(logging.NullHandler()) - import pytest import webbpsf from .. import webbpsf_core from .test_errorhandling import _exception_message_starts_with +from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test + +_log = logging.getLogger('test_webbpsf') +_log.addHandler(logging.NullHandler()) + # ------------------ NIRCam Tests ---------------------------- -from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test -test_nircam = lambda: generic_output_test('NIRCam') -test_nircam_source_offset_00 = lambda: do_test_source_offset('NIRCam', theta=0.0, monochromatic=2e-6) -test_nircam_source_offset_45 = lambda: do_test_source_offset('NIRCam', theta=45.0, monochromatic=2e-6) +def _close_enough(a, b): + # 1.5% variance accomodates the differences between the various NRC detectors in each channel + return np.isclose(a, b, rtol=0.015) + +def test_nircam(): + return generic_output_test('NIRCam') + +def test_nircam_source_offset_00(): + return do_test_source_offset('NIRCam', theta=0.0, monochromatic=2e-6) + +def test_nircam_source_offset_45(): + return do_test_source_offset('NIRCam', theta=45.0, monochromatic=2e-6) -test_nircam_set_siaf = lambda: do_test_set_position_from_siaf( - 'NIRCam', ['NRCA5_SUB160', 'NRCA3_DHSPIL_SUB96', 'NRCA5_MASKLWB_F300M', 'NRCA2_TAMASK210R'] -) +def test_nircam_set_siaf(): + return do_test_set_position_from_siaf( + 'NIRCam', ['NRCA5_SUB160', 'NRCA3_DHSPIL_SUB96', 'NRCA5_MASKLWB_F300M', 'NRCA2_TAMASK210R'] + ) -test_nircam_blc_circ_45 = lambda: do_test_nircam_blc(kind='circular', angle=45) -test_nircam_blc_circ_0 = lambda: do_test_nircam_blc(kind='circular', angle=0) +def test_nircam_blc_circ_45(): + return do_test_nircam_blc(kind='circular', angle=45) + +def test_nircam_blc_circ_0(): + return do_test_nircam_blc(kind='circular', angle=0) def test_nircam_blc_wedge_0(**kwargs): @@ -38,7 +51,6 @@ def test_nircam_blc_wedge_0(**kwargs): def test_nircam_blc_wedge_45(**kwargs): return do_test_nircam_blc(kind='linear', angle=-45, **kwargs) - # The test setup for this one is not quite right yet # See https://github.com/mperrin/webbpsf/issues/30 # and https://github.com/mperrin/poppy/issues/29 @@ -194,24 +206,20 @@ def test_nircam_get_detector(): def test_nircam_auto_pixelscale(): # This test now uses approximate equality in all the checks, to accomodate the fact that # NIRCam pixel scales are drawn directly from SIAF for the aperture, and thus vary for each detector/ - # - # 1.5% variance accomodates the differences between the various NRC detectors in each channel - close_enough = lambda a, b: np.isclose(a, b, rtol=0.015) - nc = webbpsf_core.NIRCam() nc.filter = 'F200W' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' # auto switch to long nc.filter = 'F444W' - assert close_enough(nc.pixelscale, nc._pixelscale_long) + assert _close_enough(nc.pixelscale, nc._pixelscale_long) assert nc.channel == 'long' # and it can switch back to short: nc.filter = 'F200W' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' nc.pixelscale = 0.0123 # user is allowed to set something custom @@ -222,46 +230,45 @@ def test_nircam_auto_pixelscale(): nc.pixelscale = nc._pixelscale_long # switch short again nc.filter = 'F212N' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' # And test we can switch based on detector names too nc.detector = 'NRCA5' - assert close_enough(nc.pixelscale, nc._pixelscale_long) + assert _close_enough(nc.pixelscale, nc._pixelscale_long) assert nc.channel == 'long' nc.detector = 'NRCB1' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' nc.detector = 'NRCA3' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' nc.auto_channel = False # now we can switch filters and nothing else should change: nc.filter = 'F480M' - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) assert nc.channel == 'short' # but changing the detector explicitly always updates pixelscale, regardless # of auto_channel being False nc.detector = 'NRCA5' - assert close_enough(nc.pixelscale, nc._pixelscale_long) + assert _close_enough(nc.pixelscale, nc._pixelscale_long) assert nc.channel == 'long' def test_validate_nircam_wavelengths(): # Same as above test: allow for up to 1.5% variance between NIRCam detectors in each channel - close_enough = lambda a, b: np.isclose(a, b, rtol=0.015) nc = webbpsf_core.NIRCam() # wavelengths fit on shortwave channel -> no exception nc.filter = 'F200W' nc._validate_config(wavelengths=np.linspace(nc.SHORT_WAVELENGTH_MIN, nc.SHORT_WAVELENGTH_MAX, 3)) - assert close_enough(nc.pixelscale, nc._pixelscale_short) + assert _close_enough(nc.pixelscale, nc._pixelscale_short) # short wave is selected but user tries a long wave calculation with pytest.raises(RuntimeError) as excinfo: @@ -271,7 +278,7 @@ def test_validate_nircam_wavelengths(): # wavelengths fit on long channel -> no exception nc.filter = 'F444W' nc._validate_config(wavelengths=np.linspace(nc.LONG_WAVELENGTH_MIN, nc.LONG_WAVELENGTH_MAX, 3)) - assert close_enough(nc.pixelscale, nc._pixelscale_long) + assert _close_enough(nc.pixelscale, nc._pixelscale_long) # long wave is selected but user tries a short wave calculation with pytest.raises(RuntimeError) as excinfo: @@ -395,7 +402,7 @@ def test_nircam_coron_wfe_offset(fov_pix=15, oversample=2, fit_gaussian=True): fit_gaussian = False # Ensure oversample to >1 no Gaussian fitting - if fit_gaussian == False: + if fit_gaussian is False: oversample = 2 if oversample < 2 else oversample rtol = 0.2 else: diff --git a/webbpsf/tests/test_niriss.py b/webbpsf/tests/test_niriss.py index ce6f19f2..01dc487b 100644 --- a/webbpsf/tests/test_niriss.py +++ b/webbpsf/tests/test_niriss.py @@ -1,20 +1,25 @@ import numpy as np -# _log = logging.getLogger('test_webbpsf') -# _log.addHandler(logging.NullHandler()) from .. import webbpsf_core +from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test # ------------------ NIRISS Tests ---------------------------- -from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test -test_niriss = lambda: generic_output_test('NIRISS') -test_niriss_source_offset_00 = lambda: do_test_source_offset('NIRISS', theta=0.0, monochromatic=3.0e-6) -test_niriss_source_offset_45 = lambda: do_test_source_offset('NIRISS', theta=45.0, monochromatic=3.0e-6) +def test_niriss(): + return generic_output_test('NIRISS') + +def test_niriss_source_offset_00(): + return do_test_source_offset('NIRISS', theta=0.0, monochromatic=3.0e-6) + +def test_niriss_source_offset_45(): + return do_test_source_offset('NIRISS', theta=45.0, monochromatic=3.0e-6) -test_niriss_set_siaf = lambda: do_test_set_position_from_siaf( - 'NIRISS', ['NIS_FP1MIMF', 'NIS_SUB64', 'NIS_SOSSFULL', 'NIS_SOSSTA', 'NIS_AMI1'] -) +def test_niriss_set_siaf(): + return do_test_set_position_from_siaf( + 'NIRISS', + ['NIS_FP1MIMF', 'NIS_SUB64', 'NIS_SOSSFULL', 'NIS_SOSSTA', 'NIS_AMI1'] + ) def test_niriss_auto_pupil(): diff --git a/webbpsf/tests/test_nirspec.py b/webbpsf/tests/test_nirspec.py index 53c10ee6..5da9ab21 100644 --- a/webbpsf/tests/test_nirspec.py +++ b/webbpsf/tests/test_nirspec.py @@ -4,23 +4,28 @@ import numpy as np import pysiaf +from .. import webbpsf_core +from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test + _log = logging.getLogger('test_webbpsf') _log.addHandler(logging.NullHandler()) -from .. import webbpsf_core # ------------------ NIRSpec Tests ---------------------------- -from .test_webbpsf import do_test_set_position_from_siaf, do_test_source_offset, generic_output_test - -test_nirspec = lambda: generic_output_test('NIRSpec') +def test_nirspec(): + return generic_output_test('NIRSpec') # Use a larger than typical tolerance when testing NIRSpec offsets. The # pixels are so undersampled (0.1 arcsec!) that it's unreasonable to try for # better than 1/10th of a pixel precision using default settings. -test_nirspec_source_offset_00 = lambda: do_test_source_offset('NIRSpec', theta=0.0, tolerance=0.1, monochromatic=3e-6) -test_nirspec_source_offset_45 = lambda: do_test_source_offset('NIRSpec', theta=45.0, tolerance=0.1, monochromatic=3e-6) +def test_nirspec_source_offset_00(): + return do_test_source_offset('NIRSpec', theta=0.0, tolerance=0.1, monochromatic=3e-6) + +def test_nirspec_source_offset_45(): + return do_test_source_offset('NIRSpec', theta=45.0, tolerance=0.1, monochromatic=3e-6) -test_nirspec_set_siaf = lambda: do_test_set_position_from_siaf('NIRSpec') +def test_nirspec_set_siaf(): + return do_test_set_position_from_siaf('NIRSpec') def test_nirspec_slit_apertures(): @@ -45,4 +50,4 @@ def test_calc_datacube_fast(): waves = np.linspace(3e-6, 5e-6, 3) - cube = nrs.calc_datacube_fast(waves, fov_pixels=30, oversample=1, compare_methods=True) + nrs.calc_datacube_fast(waves, fov_pixels=30, oversample=1, compare_methods=True) # TODO assert success diff --git a/webbpsf/tests/test_opds.py b/webbpsf/tests/test_opds.py index 970f43fc..9f4a478e 100644 --- a/webbpsf/tests/test_opds.py +++ b/webbpsf/tests/test_opds.py @@ -205,7 +205,7 @@ def test_thermal_slew_reproducibility(): ote.thermal_slew(12 * u.hour, start_angle=-5, end_angle=45, case='EOL') opd3 = ote.opd.copy() - assert np.allclose(opd1, opd2) == False, "OPDs expected to differ didn't" + assert np.allclose(opd1, opd2) is False, "OPDs expected to differ didn't" assert np.allclose(opd1, opd3), "OPDs expected to match didn't" @@ -343,7 +343,8 @@ def test_apply_field_dependence_model(): dependence. Thus there are several calls to manually set only the nominal field dep to True """ - rms = lambda array, mask: np.sqrt((array[mask] ** 2).mean()) + def rms(array, mask): + return np.sqrt((array[mask] ** 2).mean()) # Get the OPD without any sort of field dependence ote = webbpsf.opds.OTE_Linear_Model_WSS(v2v3=None) @@ -535,7 +536,7 @@ def test_segment_tilt_signs(fov_pix=50, plot=False, npix=1024): axs[i, 0].axhline(y=fov_pix / 2) axs[i, 0].axvline(x=fov_pix / 2) # PLOT RESULTING OPD: - im = axs[i, 1].imshow(ote.opd, vmin=-4e-6, vmax=4e-6, origin='lower') + axs[i, 1].imshow(ote.opd, vmin=-4e-6, vmax=4e-6, origin='lower') axs[i, 1].set_title('OPD (yellow +)') axs[i, 2].imshow(psfx[0].data, norm=matplotlib.colors.LogNorm(vmax=1e-2, vmin=1e-5), origin='lower') axs[i, 2].set_title(iseg + ': xtilt {} um'.format(tilt)) @@ -560,7 +561,7 @@ def test_segment_tilt_signs(fov_pix=50, plot=False, npix=1024): # PLOT RESULTING OPD: if plot: - im = axs[i, 3].imshow(ote.opd, vmin=-4e-6, vmax=4e-6, origin='lower') + axs[i, 3].imshow(ote.opd, vmin=-4e-6, vmax=4e-6, origin='lower') axs[i, 3].set_title('OPD (yellow +)') axs[i, 4].imshow(psfy[0].data, norm=matplotlib.colors.LogNorm(vmax=1e-2, vmin=1e-5), origin='lower') axs[i, 4].set_title(iseg + ': ytilt {} um'.format(tilt)) diff --git a/webbpsf/tests/test_roman.py b/webbpsf/tests/test_roman.py index 422eca37..c2b86e89 100644 --- a/webbpsf/tests/test_roman.py +++ b/webbpsf/tests/test_roman.py @@ -125,7 +125,7 @@ def test_WFI_pupil_controller(): assert wfi._pupil_controller._auto_pupil_mask, 'Pupil mask is locked and should not be' # Test pupil lock/unlock - with pytest.raises(FileNotFoundError) as err: + with pytest.raises(FileNotFoundError): assert wfi.lock_pupil('file_that_does_not_exist.fits'), 'FileNotFoundError was not raised' this_file = __file__ @@ -185,23 +185,12 @@ def test_swapping_modes(wfi=None): if wfi is None: wfi = roman.WFI() - # change detector string to match file format (e.g., "SCA01" -> "SCA_1") - detector_substr = lambda det: f'{det[:3]}_{str(int((det[3:])))}' - - # dynamically generate current pupil path for a given WFI instance - pupil_path = lambda self, mask=None: os.path.join( - self._pupil_controller._pupil_basepath, - self._pupil_controller.pupil_file_formatters[ - self._pupil_controller._get_filter_mask(self.filter) if mask is None else mask - ], - ).format(detector_substr(self.detector)) - tests = [ # [filter, mode, pupil_file] - ['F146', 'imaging', pupil_path], - ['F213', 'imaging', pupil_path], - [PRISM_FILTERS[0], 'prism', pupil_path], - [GRISM_FILTERS[0], 'grism', pupil_path], + ['F146', 'imaging', pupil_path(wfi)], + ['F213', 'imaging', pupil_path(wfi)], + [PRISM_FILTERS[0], 'prism', pupil_path(wfi)], + [GRISM_FILTERS[0], 'grism', pupil_path(wfi)], ] for test_filter, test_mode, test_pupil in tests: diff --git a/webbpsf/tests/test_utils.py b/webbpsf/tests/test_utils.py index fe3ffc58..5edea26a 100644 --- a/webbpsf/tests/test_utils.py +++ b/webbpsf/tests/test_utils.py @@ -1,20 +1,13 @@ -import astropy.io.fits as fits -import numpy as np - -try: - - _HAVE_PYTEST = True -except: - _HAVE_PYTEST = False - import logging -_log = logging.getLogger('test_webbpsf') -_log.addHandler(logging.NullHandler()) +import astropy.io.fits as fits +import numpy as np from .. import conf, utils, webbpsf_core from .test_errorhandling import _exception_message_starts_with +_log = logging.getLogger('test_webbpsf') +_log.addHandler(logging.NullHandler()) def test_logging_restart(): """Test turning off and on the logging, and then put it back the way it was.""" @@ -44,7 +37,7 @@ def test_logging_setup(): try: import pytest - except: + except ImportError: _log.warning('Skipping last step in test_logging_setup because pytest is not installed.') return # We can't do this next test if we don't have the pytest.raises function. diff --git a/webbpsf/tests/test_webbpsf.py b/webbpsf/tests/test_webbpsf.py index 9393751b..995b95b4 100644 --- a/webbpsf/tests/test_webbpsf.py +++ b/webbpsf/tests/test_webbpsf.py @@ -2,16 +2,13 @@ import os import numpy as np - -_log = logging.getLogger('test_webbpsf') -_log.addHandler(logging.NullHandler()) - - import poppy from .. import webbpsf_core from .test_errorhandling import _exception_message_starts_with +_log = logging.getLogger('test_webbpsf') +_log.addHandler(logging.NullHandler()) # The following functions are used in each of the test_ files to # test the individual SIs @@ -188,7 +185,7 @@ def test_return_intermediates(): assert isinstance(psf, astropy.io.fits.HDUList) -def do_test_set_position_from_siaf(iname, more_apertures=[]): +def do_test_set_position_from_siaf(iname, more_apertures=[]): # TODO have test assert success """Test that we can use the mapping from image mask names to aperture names to set detector positions automatically when image masks are selected.""" @@ -217,16 +214,16 @@ def test_calc_psf_format_output(): def test_instrument(): - nc = webbpsf_core.instrument('NIRCam') + webbpsf_core.instrument('NIRCam') # TODO - assert success try: import pytest - except: + except ImportError: _log.warning('Skipping last step in test_instrument because pytest is not installed.') return # We can't do this next test if we don't have the pytest.raises function. with pytest.raises(ValueError) as excinfo: - tmp = webbpsf_core.instrument('ACS') + webbpsf_core.instrument('ACS') assert _exception_message_starts_with(excinfo, 'Incorrect instrument name') diff --git a/webbpsf/tests/validate_webbpsf.py b/webbpsf/tests/validate_webbpsf.py index 9b32ccc6..0850c8eb 100644 --- a/webbpsf/tests/validate_webbpsf.py +++ b/webbpsf/tests/validate_webbpsf.py @@ -99,7 +99,7 @@ def validate_vs_russ_plot7(base_opd='OPD_RevV_nircam_155.fits'): P.draw() P.savefig('results_makidon_2007_fig7.pdf') - stop() + stop() # noqa def validate_vs_russ_plot6(nlambda=5, ax=None): @@ -369,11 +369,8 @@ def validate_vs_krist_blc(which='spot'): nc.options['no_sam'] = True nc.pixelscale = 0.065 # match the Krist sims exactly. vs 0.648 official - cor_vmin = 1e-12 - cor_vmax = 1e-5 - P.clf() - mask1 = 'nircam_4600nm_%s_occ.fits' % wedge + mask1 = 'nircam_4600nm_%s_occ.fits' % wedge # noqa TODO - no context for me to fix this mask1f = fits.open(mask1) # P.subplot(332) @@ -476,7 +473,7 @@ def validate_vs_krist_sims(clobber=False, normalize=False, which='spot', no_sam= P.colorbar(P.gca().images[0], orientation='vertical') try: fits.PrimaryHDU(trans).writeto('test_nircam_4600nm_%s_occ.fits' % which, clobber=clobber) - except: + except: # noqa - adequate errors that we are ignoring pass # ---- occulted -- @@ -522,7 +519,7 @@ def validate_vs_krist_sims(clobber=False, normalize=False, which='spot', no_sam= print('shape of %s is %s' % (my1, mypsf1[1].data.shape)) P.savefig('results_nircam_coron_comparison_%s.pdf' % which) - stop() + stop() # noqa # ---- MIRI --------------------- @@ -563,9 +560,9 @@ def validate_miri_coron(): P.subplot(211) poppy.display_PSF(im_nd_offaxis, colorbar=False) P.subplot(212) - poppyt.display_PSF(im_nd_onaxis, colorbar=False) + poppy.display_PSF(im_nd_onaxis, colorbar=False) - stop() + stop() # noqa # ---- TFI @@ -658,7 +655,7 @@ def validate_vs_jwpsf_nircam(): ('MIRI', 'F1000W', 'f1000w', '/Users/mperrin/software/jwpsf_v3.0/data/MIRI/OPD/MIRI_OPDisim1.fits', 0.11, True), ] - fig = P.figure(1, figsize=(13, 8.5), dpi=80) + P.figure(1, figsize=(13, 8.5), dpi=80) oversamp = 4 for params in models: nc = webbpsf_core.Instrument(params[0]) @@ -775,7 +772,7 @@ def ee_find_radius(ee_fn, value): ' Rev V: FWHM=%.4f EE(0.4, 0.5, 0.6) = %.3f, %0.3f, %.3f' % (mean_fwhm_v, mean_eer_v[0], mean_eer_v[1], mean_eer_v[2]) ) - stop() + stop() # noqa def compare_pupils_tv(oversample=8, vmax=1e-5, skipone=True): @@ -799,7 +796,7 @@ def compare_pupils_tv(oversample=8, vmax=1e-5, skipone=True): ) if not skipone: - ax = P.subplot(1, 3, 1) + P.subplot(1, 3, 1) poppy.display_PSF(psf_tri, normalize='peak', colorbar=False, title='Tricontagon') for i, rev in enumerate(['T', 'V']): @@ -814,7 +811,7 @@ def compare_pupils_tv(oversample=8, vmax=1e-5, skipone=True): P.subplot(1, 3, i + 2) poppy.display_PSF(psf, normalize='peak', colorbar=(rev == 'V'), title='OTE Rev ' + rev) - stop() + stop() # noqa P.clf() psf_V = fits.open('test_NIRCam_perfect_rev%s_o%d.fits' % ('V', oversample)) @@ -850,7 +847,7 @@ def compare_pupils_tv(oversample=8, vmax=1e-5, skipone=True): ax4.set_xbound(0, 4) P.draw() - stop() + stop() #noqa def validate_nircam_ee(): @@ -859,11 +856,11 @@ def validate_nircam_ee(): # Encircled energies at 1 micron, field position (0,0), # page 18 # from Code V model I think - EE_fraction = [10, 20, 30, 40, 50, 60, 70, 80, 90] - EE_radius = [0.004592, 0.009561, 0.015019, 0.021147, 0.028475, 0.037626, 0.052406, 0.081993, 0.204422] + EE_fraction = [10, 20, 30, 40, 50, 60, 70, 80, 90] # noqa remove this noqa once function is created + EE_radius = [0.004592, 0.009561, 0.015019, 0.021147, 0.028475, 0.037626, 0.052406, 0.081993, 0.204422] # noqa remove this noqa once function is created # same thing, page 24, from OSLO model - EE_radius = [0.006815, 0.013630, 0.020411, 0.026182, 0.031953, 0.037725, 0.058640, 0.090230, 0.210803] + EE_radius = [0.006815, 0.013630, 0.020411, 0.026182, 0.031953, 0.037725, 0.058640, 0.090230, 0.210803] # noqa remove this noqa once function is created # TODO write this function sometime... diff --git a/webbpsf/trending.py b/webbpsf/trending.py index 3ad0fc54..455f8424 100644 --- a/webbpsf/trending.py +++ b/webbpsf/trending.py @@ -297,9 +297,9 @@ def wfe_histogram_plot( else: full_file_path = row['fileName'] if 'rms_wfe' not in opdtable1.colnames: - if ote_only == False: + if ote_only is False: rmses.append(fits.getheader(full_file_path, ext=1)['RMS_WFE']) - elif ote_only == True: + elif ote_only is True: opd_data = fits.getdata(full_file_path, ext=1) mask = opd_data != 0 @@ -316,7 +316,6 @@ def wfe_histogram_plot( mjds = opdtable1['date_obs_mjd'] pre_or_post.append(webbpsf.mast_wss.infer_pre_or_post_correction(row)) - where_pre = ['pre' in a for a in pre_or_post] where_post = ['post' in a for a in pre_or_post] dates = astropy.time.Time(opdtable1['date'], format='isot') @@ -729,7 +728,6 @@ def single_measurement_trending_plot( sur_opd = webbpsf.opds.sur_to_opd(sur_fn, ignore_missing=ignore_missing) # cosmetic: handle masking slightly differently here to accomodate slightly different edge pixels - sur_mask = sur_opd != 0 surnanmask = nanmask.copy() surnanmask[sur_opd == 0] = np.nan @@ -960,7 +958,6 @@ def vprint(*text): wf_si = target_256 * mask dates = [] - last_visit = '' # Iterate over all selected OPDs for i, row in enumerate(opdtable[which_opds_mask]): @@ -993,7 +990,6 @@ def vprint(*text): ) axes_f = axes.flat - is_correction = np.zeros(n, bool) nanmask = np.zeros_like(wf_ote[0]) + np.nan nanmask[mask] = 1 last_date_obs = np.nan @@ -1028,7 +1024,7 @@ def vprint(*text): deltas_shown.append(delta_opd) - title = f'{date[0:10]} {date[11:16]}\n$\Delta T =${deltat * 24:.1f} hr' + title = f'{date[0:10]} {date[11:16]}\n$\Delta T =${deltat * 24:.1f} hr' # noqa if label_cid: title = f'{cid}\n' + title if label_visit: @@ -1183,7 +1179,7 @@ def get_dates_for_pid(pid, project='jwst'): start_times = astropy.time.Time(np.unique(start_times)) return start_times - except: + except: # noqa - adequate errors can be raised all needing this error print('No access to data for PID {:d}'.format(pid)) return @@ -1380,7 +1376,7 @@ def basic_show_image(image, ax, vmax=0.3, nanmask=1): (ees_at_rad - median_ee) / median_ee, ls='-', color=color, - label=f'$\Delta$EE within {ee_rad:.2f} arcsec ({ee_npix} pix)', + label=f'$\Delta$EE within {ee_rad:.2f} arcsec ({ee_npix} pix)', # noqa ) axes[1].text( @@ -1502,7 +1498,7 @@ def basic_show_image(image, ax, vmax=0.3, nanmask=1): 20, 20, f'{webbpsf.utils.rms(delta_opd, mask=apmask)*1e9:.1f}', color='yellow', fontsize=fs * 0.6 ) - for i, l in enumerate(['Measured\nWFE', 'Drifts', 'Mirror\nCorrections']): + for i, l in enumerate(['Measured\nWFE', 'Drifts', 'Mirror\nCorrections']): # noqa im_axes[i, 0].yaxis.set_visible(True) im_axes[i, 0].set_ylabel(l + '\n\n', fontsize=fs, fontweight='bold') @@ -1645,15 +1641,15 @@ def plot_wfs_obs_delta(fn1, fn2, vmax_fraction=1.0, download_opds=True): wlm8_m1 = hdul1[5].data wlp8_m1 = hdul1[10].data - wlm8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[6].data, wlm8_m1.shape) - wlp8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[11].data, wlm8_m1.shape) + wlm8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[6].data, wlm8_m1.shape) # noqa TODO - any reason not to delete? + wlp8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[11].data, wlm8_m1.shape) # noqa TODO - any reason not to delete? opd, hdul2 = webbpsf.trending._read_opd(fn2) wlm8_m2 = hdul2[5].data wlp8_m2 = hdul2[10].data - wlm8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[6].data, wlm8_m2.shape) - wlp8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[11].data, wlm8_m2.shape) + wlm8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[6].data, wlm8_m2.shape) # noqa TODO - any reason not to delete? + wlp8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[11].data, wlm8_m2.shape) # noqa TODO - any reason not to delete? cm = matplotlib.cm.inferno cm.set_bad(cm(0)) @@ -1714,6 +1710,9 @@ def plot_wfs_obs_delta(fn1, fn2, vmax_fraction=1.0, download_opds=True): return fig + + + def show_wfs_around_obs(filename, verbose='True'): """Make a helpful plot showing available WFS before and after some given science observation. This can be used to help inform how much WFE variability there was around that time. @@ -1729,7 +1728,8 @@ def show_wfs_around_obs(filename, verbose='True'): header = fits.getheader(filename) - get_datetime = lambda header: astropy.time.Time(header['DATE-OBS'] + 'T' + header['TIME-OBS']) + def get_datetime_from_header(header): + return astropy.time.Time(header['DATE-OBS'] + 'T' + header['TIME-OBS']) def vprint(*args, **kwargs): if verbose: @@ -1740,19 +1740,19 @@ def vprint(*args, **kwargs): inst.filter = header['filter'] inst.set_position_from_aperture_name(header['APERNAME']) - dateobs = get_datetime(header) + dateobs = get_datetime_from_header(header) vprint(f'File {filename} observed at {dateobs}') vprint('Retrieving WFS before that obs...', end='') inst.load_wss_opd_by_date(dateobs, choice='before', verbose=False) wfe_before = inst.get_wfe('total') - wfe_before_dateobs = get_datetime(inst.pupilopd[0].header) + wfe_before_dateobs = get_datetime_from_header(inst.pupilopd[0].header) vprint(f' WFS at {wfe_before_dateobs}') vprint('Retrieving WFS after that obs...', end='') inst.load_wss_opd_by_date(dateobs, choice='after', verbose=False) wfe_after = inst.get_wfe('total') - wfe_after_dateobs = get_datetime(inst.pupilopd[0].header) + wfe_after_dateobs = get_datetime_from_header(inst.pupilopd[0].header) vprint(f' WFS at {wfe_after_dateobs}') @@ -1897,7 +1897,7 @@ def show_wfs_during_program( # Look up wavefront sensing and mirror move corrections for that range opdtable = get_opdtable_for_daterange(start_date, end_date) - corrections_table = webbpsf.mast_wss.get_corrections(opdtable) + corrections_table = webbpsf.mast_wss.get_corrections(opdtable) # noqa TODO - this is unused and has no ramifications, is there a reason to keep or can I delete this line? # Iterate over the WFS measurements to retrieve the OPDs and RMS WFE wfs_dates = [] @@ -2017,7 +2017,7 @@ def delta_wfe_around_time(datetime, plot=True, ax=None, vmax=0.05, return_filena show_opd_image(delta_opd * nanmask, ax=ax, vmax=vmax) plt.colorbar(mappable=ax.images[0], label='WFE [microns]') - ax.set_title(f'$\Delta$WFE in the {post_delta_t - prev_delta_t:.2f} d around {datetime}') + ax.set_title(f'$\Delta$WFE in the {post_delta_t - prev_delta_t:.2f} d around {datetime}') # noqa ax.set_xlabel(f'{post_opd_fn} - \n{prev_opd_fn}') ax.set_xticks([]) ax.xaxis.set_visible(True) diff --git a/webbpsf/utils.py b/webbpsf/utils.py index 8dd15188..b500d61f 100644 --- a/webbpsf/utils.py +++ b/webbpsf/utils.py @@ -11,9 +11,10 @@ import scipy from astropy.nddata import NDData +from . import conf + _log = logging.getLogger('webbpsf') -from . import conf _DISABLE_FILE_LOGGING_VALUE = 'none' @@ -395,12 +396,12 @@ def system_diagnostic(): freq=psutil.cpu_freq()[0] / 1000, percent=psutil.cpu_percent(), ) - except: + except ImportError: try: import multiprocessing cpu_info = ' Cores: {}'.format(multiprocessing.cpu_count()) - except: + except ImportError: cpu_info = 'No CPU info available' # Get numpy config - the following is a modified version of @@ -408,7 +409,7 @@ def system_diagnostic(): numpyconfig = '' for name, info_dict in numpy.__config__.__dict__.items(): - if name[0] == '_' or type(info_dict) is not type({}): + if name[0] == '_' or not isinstance(info_dict, dict): continue numpyconfig += name + ':\n' if not info_dict: diff --git a/webbpsf/webbpsf_core.py b/webbpsf/webbpsf_core.py index 070f1910..0c4ff9fd 100644 --- a/webbpsf/webbpsf_core.py +++ b/webbpsf/webbpsf_core.py @@ -1745,6 +1745,17 @@ def load_wss_opd_by_date(self, date=None, choice='closest', verbose=True, plot=F opd_fn = webbpsf.mast_wss.get_opd_at_time(date, verbose=verbose, choice=choice, **kwargs) self.load_wss_opd(opd_fn, verbose=verbose, plot=plot, **kwargs) + def _label_wl (nwavelengths, wavelength_slices): + # Allow up to 10,000 wavelength slices. The number matters because FITS + # header keys can only have up to 8 characters. Backward-compatible. + if nwavelengths < 100: + label = 'WAVELN{:02d}'.format(wavelength_slices) + elif nwavelengths < 10000: + label = 'WVLN{:04d}'.format(wavelength_slices) + else: + raise ValueError('Maximum number of wavelengths exceeded. ' 'Cannot be more than 10,000.') + return label + def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs): """Calculate a spectral datacube of PSFs: Simplified, much MUCH faster version. @@ -1787,16 +1798,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs """ - # Allow up to 10,000 wavelength slices. The number matters because FITS - # header keys can only have up to 8 characters. Backward-compatible. nwavelengths = len(wavelengths) - if nwavelengths < 100: - label_wl = lambda i: 'WAVELN{:02d}'.format(i) - elif nwavelengths < 10000: - label_wl = lambda i: 'WVLN{:04d}'.format(i) - else: - raise ValueError('Maximum number of wavelengths exceeded. ' 'Cannot be more than 10,000.') - # Set up cube and initialize structure based on PSF at first wavelength poppy.poppy_core._log.info('Starting multiwavelength data cube calculation.') REF_WAVE = 2e-6 # This must not be too short, to avoid phase wrapping for the C3 bump @@ -1810,7 +1812,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs ext = 0 cubefast[ext].data = np.zeros((nwavelengths, psf[ext].data.shape[0], psf[ext].data.shape[1])) cubefast[ext].data[0] = psf[ext].data - cubefast[ext].header[label_wl(0)] = wavelengths[0] + cubefast[ext].header[self._label_wl(nwavelengths, 0)] = wavelengths[0] ### Fast way. Assumes wavelength-independent phase and amplitude at the exit pupil!! if compare_methods: @@ -1856,7 +1858,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs for ext in range(len(psf)): cube[ext].data = np.zeros((nwavelengths, psf[ext].data.shape[0], psf[ext].data.shape[1])) cube[ext].data[0] = psf[ext].data - cube[ext].header[label_wl(0)] = wavelengths[0] + cube[ext].header[self._label_wl(nwavelengths, 0)] = wavelengths[0] # iterate rest of wavelengths print('Running standard way') @@ -1865,7 +1867,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs psf = self.calc_psf(*args, monochromatic=wl, **kwargs) for ext in range(len(psf)): cube[ext].data[i] = psf[ext].data - cube[ext].header[label_wl(i)] = wl + cube[ext].header[self._label_wl(nwavelengths, i)] = wl cube[ext].header.add_history('--- Cube Plane {} ---'.format(i)) for h in psf[ext].header['HISTORY']: cube[ext].header.add_history(h) @@ -1980,7 +1982,7 @@ def _addAdditionalOptics(self, optsys, oversample=2): # This approach is required computationally so we can work in an unrotated frame # aligned with the FQPM axes. - defaultpupil = optsys.planes.pop(2) # throw away the rotation of the entrance pupil we just added + optsys.planes.pop(2) # throw away the rotation of the entrance pupil we just added if self.include_si_wfe: # temporarily remove the SI internal aberrations @@ -2671,11 +2673,11 @@ def _addAdditionalOptics(self, optsys, oversample=2): shift_x, shift_y = self._get_pupil_shift() rotation = self.options.get('pupil_rotation', None) - # NIRCam as-built weak lenses, from WSS config file, PRDOPSFLT-027 - WLP4_diversity = 8.3443 # microns - WLP8_diversity = 16.5932 # microns - WLM8_diversity = -16.5593 # microns - WL_wavelength = 2.12 # microns + # NIRCam as-built weak lenses, from WSS config file, PRDOPSFLT-027 # TODO - Okay to delete these unused defines? + # WLP4_diversity = 8.3443 # microns + # WLP8_diversity = 16.5932 # microns + # WLM8_diversity = -16.5593 # microns + # WL_wavelength = 2.12 # microns if self.pupil_mask == 'CIRCLYOT' or self.pupil_mask == 'MASKRND': optsys.add_pupil( From 7fc75b93f8ffa7d0209a97b5e539bdf4e1b41f6a Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 10:40:48 -0400 Subject: [PATCH 3/8] add self to _label_wl --- webbpsf/webbpsf_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webbpsf/webbpsf_core.py b/webbpsf/webbpsf_core.py index 0c4ff9fd..b3552a5f 100644 --- a/webbpsf/webbpsf_core.py +++ b/webbpsf/webbpsf_core.py @@ -1745,7 +1745,7 @@ def load_wss_opd_by_date(self, date=None, choice='closest', verbose=True, plot=F opd_fn = webbpsf.mast_wss.get_opd_at_time(date, verbose=verbose, choice=choice, **kwargs) self.load_wss_opd(opd_fn, verbose=verbose, plot=plot, **kwargs) - def _label_wl (nwavelengths, wavelength_slices): + def _label_wl (self, nwavelengths, wavelength_slices): # Allow up to 10,000 wavelength slices. The number matters because FITS # header keys can only have up to 8 characters. Backward-compatible. if nwavelengths < 100: From 339ea1226a2348f944660c8b1b4cc0bd24f8954f Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 11:21:29 -0400 Subject: [PATCH 4/8] fix pupil_path call in test_swapping_modes --- webbpsf/tests/test_roman.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webbpsf/tests/test_roman.py b/webbpsf/tests/test_roman.py index c2b86e89..2e8d7bf1 100644 --- a/webbpsf/tests/test_roman.py +++ b/webbpsf/tests/test_roman.py @@ -187,10 +187,10 @@ def test_swapping_modes(wfi=None): tests = [ # [filter, mode, pupil_file] - ['F146', 'imaging', pupil_path(wfi)], - ['F213', 'imaging', pupil_path(wfi)], - [PRISM_FILTERS[0], 'prism', pupil_path(wfi)], - [GRISM_FILTERS[0], 'grism', pupil_path(wfi)], + ['F146', 'imaging', pupil_path], + ['F213', 'imaging', pupil_path], + [PRISM_FILTERS[0], 'prism', pupil_path], + [GRISM_FILTERS[0], 'grism', pupil_path], ] for test_filter, test_mode, test_pupil in tests: From fb9dca534d24f187f4a99b17e2723d12b9de3181 Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 14:30:04 -0400 Subject: [PATCH 5/8] feedback updates --- webbpsf/gridded_library.py | 12 ++-------- webbpsf/opds.py | 46 -------------------------------------- webbpsf/optical_budget.py | 4 ---- webbpsf/optics.py | 4 +--- webbpsf/tests/test_opds.py | 4 ++-- webbpsf/trending.py | 10 +-------- webbpsf/utils.py | 12 ++++++++++ webbpsf/webbpsf_core.py | 24 ++++---------------- 8 files changed, 22 insertions(+), 94 deletions(-) diff --git a/webbpsf/gridded_library.py b/webbpsf/gridded_library.py index df83d62c..35cf6788 100644 --- a/webbpsf/gridded_library.py +++ b/webbpsf/gridded_library.py @@ -4,10 +4,11 @@ import astropy.convolution import numpy as np +import poppy from astropy.io import fits from astropy.nddata import NDData +from photutils.psf import GriddedPSFModel -import poppy import webbpsf.detectors @@ -150,15 +151,6 @@ def __init__( """ - # Before doing anything else, check that we have GriddedPSFModel - try: - from photutils.psf import GriddedPSFModel - except ImportError: - try: - from photutils import GriddedPSFModel # noqa - except ImportError: - raise ImportError('This method requires photutils >= 0.6') - # Pull WebbPSF instance self.webb = instrument self.instr = instrument.name diff --git a/webbpsf/opds.py b/webbpsf/opds.py index 20c76d69..02557f69 100644 --- a/webbpsf/opds.py +++ b/webbpsf/opds.py @@ -2972,52 +2972,6 @@ def random_unstack(ote, radius=1, verbose=False): ote.update_opd(verbose=verbose) -# -------------------------------------------------------------------------------- - - -def test_OPDbender(): - plt.figure(1) - tel = OPDbender() # TODO remove this whole test? - tel.displace('A1', 1, 0, 0, display=False) - tel.displace('A2', 0, 1, 0, display=False) - tel.displace('A3', 0, 0, 0.03, display=False) - tel.displace('A4', 0, -1, 0, display=False) - tel.displace('A5', 1, -1, 0, display=False) - - tel.tilt('B1', 0.1, 0, 0) - tel.tilt('B2', 0, 0.1, 0) - tel.tilt('B3', 0, 0, 100) - - tel.display() - - plt.figure(2) - tel.zern_seg('B3') - - print('') - print('') - print('RMS WFE is ', tel.rms()) - - tel.print_state() - - -def test2_OPDbender(filename='OPD_RevV_nircam_132.fits'): - orig = OPDbender(filename) # TODO remove this whole test? - - plot_kwargs = {'colorbar_orientation': 'horizontal', 'clear': False} - - plt.clf() - plt.subplot(131) - orig.draw(title='Input OPD from \n' + filename, **plot_kwargs) - - perturbed = orig.copy() - perturbed.perturb_all(multiplier=0.2, draw=False) - - plt.subplot(132) - perturbed.draw(title='OPD after small random perturbation', **plot_kwargs) - - plt.subplot(133) - diff = perturbed - orig - diff.draw(title='Difference ({0:.1f} nm rms)'.format(diff.rms()), **plot_kwargs) # ------------------------------------------------------------------------------- diff --git a/webbpsf/optical_budget.py b/webbpsf/optical_budget.py index af43b948..0236a439 100644 --- a/webbpsf/optical_budget.py +++ b/webbpsf/optical_budget.py @@ -426,10 +426,6 @@ def bounds(axis): between_col2_col3, meany_row, '+', color='purple', horizontalalignment='center', fontweight='bold', fontsize=18 ) - # Annotate first column to show WFE summation - between_row1_row2 = (bounds(axes[1, 0])[0, 1] + bounds(axes[2, 0])[1, 1]) / 2 # TODO - Unused values okay to delete? - between_row2_row3 = (bounds(axes[2, 0])[0, 1] + bounds(axes[3, 0])[1, 1]) / 2 # TODO - Unused values okay to delete? - meanx_col0 = bounds(axes[0, 0])[:, 0].mean() for irow, symbol in enumerate(['=', '+', '+']): between_rows = (bounds(axes[irow, 0])[0, 1] + bounds(axes[irow + 1, 0])[1, 1]) / 2 diff --git a/webbpsf/optics.py b/webbpsf/optics.py index bb947df4..73bfdb44 100644 --- a/webbpsf/optics.py +++ b/webbpsf/optics.py @@ -233,8 +233,6 @@ def get_transmission(self, wave): The walls separating adjacent shutters are 0.06 arcsec wide. """ - msa_width = 0.2 # TODO - Unused values okay to delete? - msa_height = 0.45 # TODO - Unused values okay to delete? msa_wall = 0.06 msa_x_pitch = 0.26 msa_y_pitch = 0.51 @@ -841,7 +839,7 @@ def get_transmission(self, wave): if poppy.accel_math._USE_NUMEXPR: import numexpr as ne - jn1 = scipy.special.j1(sigmar) # noqa TODO - this looks like a bug, should jn1 be formatted in the following line? + jn1 = scipy.special.j1(sigmar) # noqa self.transmission = ne.evaluate('(1 - (2 * jn1 / sigmar) ** 2)') else: self.transmission = 1 - (2 * scipy.special.j1(sigmar) / sigmar) ** 2 diff --git a/webbpsf/tests/test_opds.py b/webbpsf/tests/test_opds.py index 9f4a478e..a56dcb69 100644 --- a/webbpsf/tests/test_opds.py +++ b/webbpsf/tests/test_opds.py @@ -12,6 +12,8 @@ import webbpsf +from ..utils import rms + # Set up a pinned pysiaf version so as not to break tests with any pysiaf value updates prd_data_dir = pysiaf.constants.JWST_PRD_DATA_ROOT.rsplit('PRD', 1)[0] PRD34_NRC = os.path.join(prd_data_dir, 'PRDOPSSOC-034/SIAFXML/SIAFXML/NIRCam_SIAF.xml') @@ -343,8 +345,6 @@ def test_apply_field_dependence_model(): dependence. Thus there are several calls to manually set only the nominal field dep to True """ - def rms(array, mask): - return np.sqrt((array[mask] ** 2).mean()) # Get the OPD without any sort of field dependence ote = webbpsf.opds.OTE_Linear_Model_WSS(v2v3=None) diff --git a/webbpsf/trending.py b/webbpsf/trending.py index 455f8424..189a4509 100644 --- a/webbpsf/trending.py +++ b/webbpsf/trending.py @@ -1641,15 +1641,11 @@ def plot_wfs_obs_delta(fn1, fn2, vmax_fraction=1.0, download_opds=True): wlm8_m1 = hdul1[5].data wlp8_m1 = hdul1[10].data - wlm8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[6].data, wlm8_m1.shape) # noqa TODO - any reason not to delete? - wlp8_c1 = poppy.utils.pad_or_crop_to_shape(hdul1[11].data, wlm8_m1.shape) # noqa TODO - any reason not to delete? opd, hdul2 = webbpsf.trending._read_opd(fn2) wlm8_m2 = hdul2[5].data wlp8_m2 = hdul2[10].data - wlm8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[6].data, wlm8_m2.shape) # noqa TODO - any reason not to delete? - wlp8_c2 = poppy.utils.pad_or_crop_to_shape(hdul2[11].data, wlm8_m2.shape) # noqa TODO - any reason not to delete? cm = matplotlib.cm.inferno cm.set_bad(cm(0)) @@ -1710,9 +1706,6 @@ def plot_wfs_obs_delta(fn1, fn2, vmax_fraction=1.0, download_opds=True): return fig - - - def show_wfs_around_obs(filename, verbose='True'): """Make a helpful plot showing available WFS before and after some given science observation. This can be used to help inform how much WFE variability there was around that time. @@ -1897,8 +1890,7 @@ def show_wfs_during_program( # Look up wavefront sensing and mirror move corrections for that range opdtable = get_opdtable_for_daterange(start_date, end_date) - corrections_table = webbpsf.mast_wss.get_corrections(opdtable) # noqa TODO - this is unused and has no ramifications, is there a reason to keep or can I delete this line? - + # Iterate over the WFS measurements to retrieve the OPDs and RMS WFE wfs_dates = [] rms_obs = [] diff --git a/webbpsf/utils.py b/webbpsf/utils.py index b500d61f..d2a7e47b 100644 --- a/webbpsf/utils.py +++ b/webbpsf/utils.py @@ -995,3 +995,15 @@ def determine_inst_name_from_v2v3(v2v3): raise ValueError(f'Given V2V3 coordinates {v2v3} do not fall within an instrument FOV region') return instrument + + +def label_wavelength (nwavelengths, wavelength_slices): + # Allow up to 10,000 wavelength slices. The number matters because FITS + # header keys can only have up to 8 characters. Backward-compatible. + if nwavelengths < 100: + label = 'WAVELN{:02d}'.format(wavelength_slices) + elif nwavelengths < 10000: + label = 'WVLN{:04d}'.format(wavelength_slices) + else: + raise ValueError('Maximum number of wavelengths exceeded. ' 'Cannot be more than 10,000.') + return label diff --git a/webbpsf/webbpsf_core.py b/webbpsf/webbpsf_core.py index b3552a5f..53af6a45 100644 --- a/webbpsf/webbpsf_core.py +++ b/webbpsf/webbpsf_core.py @@ -35,6 +35,7 @@ import scipy.ndimage import webbpsf.mast_wss +from webbpsf.utils import label_wavelength from . import DATA_VERSION_MIN, constants, detectors, distortion, gridded_library, opds, optics, utils @@ -1745,17 +1746,6 @@ def load_wss_opd_by_date(self, date=None, choice='closest', verbose=True, plot=F opd_fn = webbpsf.mast_wss.get_opd_at_time(date, verbose=verbose, choice=choice, **kwargs) self.load_wss_opd(opd_fn, verbose=verbose, plot=plot, **kwargs) - def _label_wl (self, nwavelengths, wavelength_slices): - # Allow up to 10,000 wavelength slices. The number matters because FITS - # header keys can only have up to 8 characters. Backward-compatible. - if nwavelengths < 100: - label = 'WAVELN{:02d}'.format(wavelength_slices) - elif nwavelengths < 10000: - label = 'WVLN{:04d}'.format(wavelength_slices) - else: - raise ValueError('Maximum number of wavelengths exceeded. ' 'Cannot be more than 10,000.') - return label - def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs): """Calculate a spectral datacube of PSFs: Simplified, much MUCH faster version. @@ -1812,7 +1802,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs ext = 0 cubefast[ext].data = np.zeros((nwavelengths, psf[ext].data.shape[0], psf[ext].data.shape[1])) cubefast[ext].data[0] = psf[ext].data - cubefast[ext].header[self._label_wl(nwavelengths, 0)] = wavelengths[0] + cubefast[ext].header[label_wavelength(nwavelengths, 0)] = wavelengths[0] ### Fast way. Assumes wavelength-independent phase and amplitude at the exit pupil!! if compare_methods: @@ -1858,7 +1848,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs for ext in range(len(psf)): cube[ext].data = np.zeros((nwavelengths, psf[ext].data.shape[0], psf[ext].data.shape[1])) cube[ext].data[0] = psf[ext].data - cube[ext].header[self._label_wl(nwavelengths, 0)] = wavelengths[0] + cube[ext].header[label_wavelength(nwavelengths, 0)] = wavelengths[0] # iterate rest of wavelengths print('Running standard way') @@ -1867,7 +1857,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, *args, **kwargs psf = self.calc_psf(*args, monochromatic=wl, **kwargs) for ext in range(len(psf)): cube[ext].data[i] = psf[ext].data - cube[ext].header[self._label_wl(nwavelengths, i)] = wl + cube[ext].header[label_wavelength(nwavelengths, i)] = wl cube[ext].header.add_history('--- Cube Plane {} ---'.format(i)) for h in psf[ext].header['HISTORY']: cube[ext].header.add_history(h) @@ -2673,12 +2663,6 @@ def _addAdditionalOptics(self, optsys, oversample=2): shift_x, shift_y = self._get_pupil_shift() rotation = self.options.get('pupil_rotation', None) - # NIRCam as-built weak lenses, from WSS config file, PRDOPSFLT-027 # TODO - Okay to delete these unused defines? - # WLP4_diversity = 8.3443 # microns - # WLP8_diversity = 16.5932 # microns - # WLM8_diversity = -16.5593 # microns - # WL_wavelength = 2.12 # microns - if self.pupil_mask == 'CIRCLYOT' or self.pupil_mask == 'MASKRND': optsys.add_pupil( transmission=self._datapath + '/optics/NIRCam_Lyot_Somb.fits.gz', From 796c7e685473c1e831d010bf77f6182424146ca0 Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 14:57:12 -0400 Subject: [PATCH 6/8] updates from discussion --- webbpsf/opds.py | 93 --------------------------------------- webbpsf/optical_budget.py | 4 +- webbpsf/optics.py | 2 +- webbpsf/trending.py | 12 ++--- 4 files changed, 9 insertions(+), 102 deletions(-) diff --git a/webbpsf/opds.py b/webbpsf/opds.py index 02557f69..7313c522 100644 --- a/webbpsf/opds.py +++ b/webbpsf/opds.py @@ -246,99 +246,6 @@ def writeto(self, outname, overwrite=True, **kwargs): """Write OPD to a FITS file on disk""" self.as_fits(**kwargs).writeto(outname, overwrite=overwrite) - # ---- display and analysis - def powerspectrum(self, max_cycles=50, sampling=5, vmax=100, iterate=False): - """ - Compute the spatial power spectrum of an aberrated wavefront. - - Produces nice plots on screen. - - Returns an array [low, mid, high] giving the RMS spatial frequencies in the - different JWST-defined spatial frequency bins: - low: <=5 cycles/aperture - mid: 5 < cycles <= 30 - high: 30 < cycles - - """ - - import SFT - - def tvcircle(radius=1, xcen=0, ycen=0, center=None, **kwargs): - """ - draw a circle on an image. - - radius - xcen - ycen - center= tuple in (Y,X) order. - """ - if center is not None: - xcen = center[1] - ycen = center[0] - t = np.arange(0, np.pi * 2.0, 0.01) - t = t.reshape((len(t), 1)) - x = radius * np.cos(t) + xcen - y = radius * np.sin(t) + ycen - plt.plot(x, y, **kwargs) - - cmap = copy.copy(matplotlib.cm.get_cmap(poppy.conf.cmap_diverging)) - cmap.set_bad('0.3') - - plt.clf() - plt.subplot(231) - self.display(title='full wavefront', clear=False, colorbar=False, vmax=vmax) - - ps_pixel_size = 1.0 / sampling # how many cycles per pixel - trans = SFT.SFT3(self.data, max_cycles * 2, max_cycles * 2 * sampling) - - abstrans = np.abs(trans) - - extent = [-max_cycles, max_cycles, -max_cycles, max_cycles] - - plt.subplot(233) - plt.imshow(abstrans, extent=extent, origin='lower') - plt.title('Power Spectrum of the phase') - plt.ylabel('cycles/aperture') - tvcircle(radius=5, color='k', linewidth=1) # , ls='--') - tvcircle(radius=30, color='k', linewidth=1) # 2, ls='--') - plt.gca().set_xbound(-max_cycles, max_cycles) - plt.gca().set_ybound(-max_cycles, max_cycles) - - y, x = np.indices(abstrans.shape) - y -= abstrans.shape[0] / 2.0 - x -= abstrans.shape[1] / 2.0 - r = np.sqrt(x**2 + y**2) * ps_pixel_size - - mask = np.ones_like(self.data) - mask[np.where(self.amplitude == 0)] = np.nan - wgood = np.where(self.amplitude != 0) - - components = [] - for i, label in enumerate(['low', 'mid', 'high']): - plt.subplot(2, 3, i + 4) - if label == 'low': - condition = r <= 5 - elif label == 'mid': - condition = (r > 5) & (r <= 30) - else: - condition = r > 30 - filtered = trans * condition - - inverse = SFT.SFT3(filtered, max_cycles * 2, self.opd.shape[0], inverse=True) - inverse = inverse[ - ::-1, ::-1 - ] # I thought SFT did this but apparently this is necessary to get the high freqs right... - - plt.imshow( - inverse.real * mask, vmin=(-vmax) / 1000.0, vmax=vmax / 1000, cmap=cmap, origin='lower' - ) # vmax is in nm, but WFE is in microns, so convert - plt.title(label + ' spatial frequencies') - rms = np.sqrt((inverse.real[wgood] ** 2).mean()) * 1000 - - components.append(rms) - plt.xlabel('%.3f nm RMS WFE' % rms) - - return np.asarray(components) def display_opd( self, diff --git a/webbpsf/optical_budget.py b/webbpsf/optical_budget.py index 0236a439..2ac23a15 100644 --- a/webbpsf/optical_budget.py +++ b/webbpsf/optical_budget.py @@ -346,7 +346,7 @@ def vprint(x): ax.text( 0.96, 0.92, - f'45$^\circ$, {slew_delta_time}', # noqa + f'45$^\\circ$, {slew_delta_time}', transform=ax.transAxes, horizontalalignment='right', ) @@ -361,7 +361,7 @@ def vprint(x): annotate_budget='Image motion (as equiv. WFE)', instname=inst.name, ) - ax.text(0.04, 0.92, f'LOS jitter: {jitter}, 1$\sigma$/axis', transform=ax.transAxes) # noqa sigma not escape char + ax.text(0.04, 0.92, f'LOS jitter: {jitter}, 1$\\sigma$/axis', transform=ax.transAxes) # ISIM and SI show_opd( diff --git a/webbpsf/optics.py b/webbpsf/optics.py index 73bfdb44..bb1f9af6 100644 --- a/webbpsf/optics.py +++ b/webbpsf/optics.py @@ -839,7 +839,7 @@ def get_transmission(self, wave): if poppy.accel_math._USE_NUMEXPR: import numexpr as ne - jn1 = scipy.special.j1(sigmar) # noqa + jn1 = scipy.special.j1(sigmar) # noqa - used via ne.evaluate() self.transmission = ne.evaluate('(1 - (2 * jn1 / sigmar) ** 2)') else: self.transmission = 1 - (2 * scipy.special.j1(sigmar) / sigmar) ** 2 diff --git a/webbpsf/trending.py b/webbpsf/trending.py index 189a4509..034dc554 100644 --- a/webbpsf/trending.py +++ b/webbpsf/trending.py @@ -1024,7 +1024,7 @@ def vprint(*text): deltas_shown.append(delta_opd) - title = f'{date[0:10]} {date[11:16]}\n$\Delta T =${deltat * 24:.1f} hr' # noqa + title = f'{date[0:10]} {date[11:16]}\n$\\Delta T =${deltat * 24:.1f} hr' if label_cid: title = f'{cid}\n' + title if label_visit: @@ -1376,7 +1376,7 @@ def basic_show_image(image, ax, vmax=0.3, nanmask=1): (ees_at_rad - median_ee) / median_ee, ls='-', color=color, - label=f'$\Delta$EE within {ee_rad:.2f} arcsec ({ee_npix} pix)', # noqa + label=f'$\\Delta$EE within {ee_rad:.2f} arcsec ({ee_npix} pix)', ) axes[1].text( @@ -1498,9 +1498,9 @@ def basic_show_image(image, ax, vmax=0.3, nanmask=1): 20, 20, f'{webbpsf.utils.rms(delta_opd, mask=apmask)*1e9:.1f}', color='yellow', fontsize=fs * 0.6 ) - for i, l in enumerate(['Measured\nWFE', 'Drifts', 'Mirror\nCorrections']): # noqa + for i, label in enumerate(['Measured\nWFE', 'Drifts', 'Mirror\nCorrections']): im_axes[i, 0].yaxis.set_visible(True) - im_axes[i, 0].set_ylabel(l + '\n\n', fontsize=fs, fontweight='bold') + im_axes[i, 0].set_ylabel(label + '\n\n', fontsize=fs, fontweight='bold') for j in range(npoints, min_n_im_axes): for i in range(3): @@ -1890,7 +1890,7 @@ def show_wfs_during_program( # Look up wavefront sensing and mirror move corrections for that range opdtable = get_opdtable_for_daterange(start_date, end_date) - + # Iterate over the WFS measurements to retrieve the OPDs and RMS WFE wfs_dates = [] rms_obs = [] @@ -2009,7 +2009,7 @@ def delta_wfe_around_time(datetime, plot=True, ax=None, vmax=0.05, return_filena show_opd_image(delta_opd * nanmask, ax=ax, vmax=vmax) plt.colorbar(mappable=ax.images[0], label='WFE [microns]') - ax.set_title(f'$\Delta$WFE in the {post_delta_t - prev_delta_t:.2f} d around {datetime}') # noqa + ax.set_title(f'$\\Delta$WFE in the {post_delta_t - prev_delta_t:.2f} d around {datetime}') ax.set_xlabel(f'{post_opd_fn} - \n{prev_opd_fn}') ax.set_xticks([]) ax.xaxis.set_visible(True) From bf1638ca11d51034e42105ed3142bd47d9cf556e Mon Sep 17 00:00:00 2001 From: Bradley Sappington Date: Tue, 14 May 2024 16:08:34 -0400 Subject: [PATCH 7/8] remove ruff specifics --- pyproject.toml | 84 -------------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82ef6533..52c70082 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,87 +134,3 @@ omit = [ "*/webbpsf/*/*/tests/*", "*/webbpsf/version*", ] - -# Ensure ruff is in line with .pep8speaks.yml -[tool.ruff] -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".ipynb_checkpoints", - ".mypy_cache", - ".nox", - ".pants.d", - ".pyenv", - ".pytest_cache", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - ".vscode", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "site-packages", - "venv", -] - -line-length = 125 -indent-width = 4 - -# Assume Python 3.10 -target-version = "py310" - -[tool.ruff.lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F", "W"] -ignore = [ - "W191", # indentation contains tabs - "W505", # doc line too long - "E401", # multiple imports on one line -] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -ignore-init-module-imports = true - -[tool.ruff.format] -# Like Black, use double quotes for strings. -quote-style = "single" - -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" - -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. -line-ending = "auto" - -# Enable auto-formatting of code examples in docstrings. Markdown, -# reStructuredText code/literal blocks and doctests are all supported. -# -# This is currently disabled by default, but it is planned for this -# to be opt-out in the future. -docstring-code-format = true - -# Set the line length limit used when formatting code snippets in -# docstrings. -# -# This only has an effect when the `docstring-code-format` setting is -# enabled. -docstring-code-line-length = "dynamic" From fb960d9aacb9b74ce54e4ca79e1074086faca28d Mon Sep 17 00:00:00 2001 From: Marshall Perrin Date: Fri, 17 May 2024 13:15:24 -0400 Subject: [PATCH 8/8] fix error introduced during merge deconflict --- webbpsf/webbpsf_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webbpsf/webbpsf_core.py b/webbpsf/webbpsf_core.py index 13e0b8c9..e0850505 100644 --- a/webbpsf/webbpsf_core.py +++ b/webbpsf/webbpsf_core.py @@ -1891,7 +1891,7 @@ def calc_datacube_fast(self, wavelengths, compare_methods=False, outfile=None, * wl = wavelengths[i] psfw = quickosys.calc_psf(wavelength=wl, normalize='None') cubefast[0].data[i] = psfw[0].data - cubefast[ext].header[label_wl(i)] = wavelengths[i] + cubefast[ext].header[label_wavelength(nwavelengths, i)] = wavelengths[i] cubefast[0].header['NWAVES'] = nwavelengths