From 5c5bf7a6754ab204ad72b144a50070ceb5f81487 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 19 Aug 2024 12:56:46 -0700 Subject: [PATCH 01/37] first attempt --- jwst/cube_build/cube_build_step.py | 36 ++++++++++++++++++++++++++++++ jwst/cube_build/ifu_cube.py | 34 +++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index c675ae338f..e30b719721 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -9,6 +9,7 @@ from . import cube_build from . import ifu_cube from . import data_types +import asdf from ..assign_wcs.util import update_s_region_keyword from ..stpipe import Step, record_step_status @@ -64,6 +65,7 @@ class CubeBuildStep (Step): search_output_file = boolean(default=false) output_use_model = boolean(default=true) # Use filenames in the output models suffix = string(default='s3d') + offset_list = string(default=None) debug_spaxel = string(default='-1 -1 -1') # Default not used """ @@ -236,6 +238,17 @@ def process(self, input): self.pars_input['output_type'] = self.output_type self.log.info(f'Setting output type to: {self.output_type}') +# ________________________________________________________________________________ +# If an offset file is provided do some basic checks on the file and its contents + self.offsets = None + + if self.offset_list is not None: + offsets = self.check_offset_list() + + if offsets is not None: + print(offsets) + self.offsets = offsets +# ________________________________________________________________________________ # Read in Cube Parameter Reference file # identify what reference file has been associated with these input @@ -276,6 +289,7 @@ def process(self, input): 'roiw': self.roiw, 'wavemin': self.wavemin, 'wavemax': self.wavemax, + 'offsets':self.offsets, 'skip_dqflagging': self.skip_dqflagging, 'suffix': self.suffix, 'debug_spaxel': self.debug_spaxel} @@ -530,3 +544,25 @@ def read_user_input(self): # remove duplicates if needed self.pars_input['grating'] = list(set(self.pars_input['grating'])) # ________________________________________________________________________________ + + def check_offset_list(self): + # first check file is asdf + + check_asdf = asdf.util.get_file_type(asdf.generic_io.get_file(self.offset_list)) + if check_asdf == asdf.util.FileType.ASDF: + with asdf.open(self.offset_list) as af: + offsets = af.tree['offsets'] + + for model in self.input_models: + print(model.meta.filename) + file_check = model.meta.filename + if file_check in offsets['filename']: + #print('found file', file_check) + continue + else: + print('In file from association not found in offset list') + print(offsets['filename']) + return None + return offsets + + diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index eb66482dfa..ec606b2e1e 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -69,6 +69,7 @@ def __init__(self, self.cube_pa = pars_cube.get('cube_pa') self.nspax_x = pars_cube.get('nspax_x') self.nspax_y = pars_cube.get('nspax_y') + self.offsets = pars_cube.get('offsets') self.rois = pars_cube.get('rois') self.roiw = pars_cube.get('roiw') self.debug_spaxel = pars_cube.get('debug_spaxel') @@ -1515,9 +1516,12 @@ def map_detector_to_outputframe(self, this_par1, scalerad_det = None x_det = None y_det = None - + offsets = self.offsets + + if self.instrument == 'MIRI': - sky_result = self.map_miri_pixel_to_sky(input_model, this_par1, subtract_background) + sky_result = self.map_miri_pixel_to_sky(input_model, this_par1, subtract_background, + offsets) (x, y, ra, dec, wave_all, slice_no_all, dwave_all, corner_coord_all) = sky_result elif self.instrument == 'NIRSPEC': @@ -1692,7 +1696,8 @@ def map_detector_to_outputframe(self, this_par1, softrad_det, scalerad_det, x_det, y_det # ______________________________________________________________________ - def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background): + def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, + offsets): """Loop over a file and map the detector pixels to the output cube The output frame is on the SKY (ra-dec) @@ -1719,6 +1724,17 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background): dwave = None corner_coord = None + raoffset = 0.0 + decoffset = 0.0 + # pull out ra dec offset if it exists + if offsets is not None: + filename = input_model.meta.filename + index = offsets['filename'].index(filename) + raoffset = offsets['raoffset'][index]/3600.0 + decoffset = offsets['decoffset'][index]/3600.0 + + print('ra and dec offset to apply in degrees', raoffset, decoffset) + # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been # subtracted - subtract it. @@ -1754,6 +1770,8 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background): # if self.coord_system == 'skyalign' or self.coord_system == 'ifualign': ra, dec, wave = input_model.meta.wcs(x, y) + ra = ra + raoffset + dec = dec + decoffset valid1 = ~np.isnan(ra) ra = ra[valid1] dec = dec[valid1] @@ -1793,6 +1811,16 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background): input_model.meta.wcs.output_frame, alpha2, beta - dbeta * pixfrac / 2., wave) + ra1 = ra1 + raoffset + ra2 = ra2 + raoffset + ra3 = ra3 + raoffset + ra4 = ra4 + raoffset + + dec1 = dec1 + decoffset + dec2 = dec2 + decoffset + dec3 = dec3 + decoffset + dec4 = dec4 + decoffset + corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) From cbc2a50b832dc13f7bf184e4bee4f133ea2dba16 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Wed, 21 Aug 2024 13:20:55 -0700 Subject: [PATCH 02/37] First attempt at adding offsets --- jwst/cube_build/cube_build_step.py | 54 +++++++++++++++++++++--------- jwst/cube_build/ifu_cube.py | 45 +++++++++++++++++-------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index e30b719721..5726c4af6c 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -239,14 +239,15 @@ def process(self, input): self.log.info(f'Setting output type to: {self.output_type}') # ________________________________________________________________________________ -# If an offset file is provided do some basic checks on the file and its contents +# If an offset file is provided do some basic checks on the file and its contents. +# The offset list contains a matching list to the files in the association +# used in calspec3 (or offline cube building). +# Each row in the offset list contain a filename, ra offset and dec offset. +# The offset list is an asdf file. self.offsets = None - if self.offset_list is not None: offsets = self.check_offset_list() - if offsets is not None: - print(offsets) self.offsets = offsets # ________________________________________________________________________________ # Read in Cube Parameter Reference file @@ -289,7 +290,7 @@ def process(self, input): 'roiw': self.roiw, 'wavemin': self.wavemin, 'wavemax': self.wavemax, - 'offsets':self.offsets, + 'offsets': self.offsets, 'skip_dqflagging': self.skip_dqflagging, 'suffix': self.suffix, 'debug_spaxel': self.debug_spaxel} @@ -546,23 +547,46 @@ def read_user_input(self): # ________________________________________________________________________________ def check_offset_list(self): - # first check file is asdf - + """Read in an optional ra and dec offsets for each file. + + Summary + ---------- + Check that is file is asdf file. + check the file has the correct format: + For each file in the input assocation check that there is a corresponding + file in the offset file. + Also check that each file in the offset list contain a ra offset and dec offset. + + """ + check_asdf = asdf.util.get_file_type(asdf.generic_io.get_file(self.offset_list)) if check_asdf == asdf.util.FileType.ASDF: with asdf.open(self.offset_list) as af: offsets = af.tree['offsets'] - + + format_failure = False + # Currently the offset list has to have the following keys: filename, raoffset, decoffset + if 'filename' not in offsets.keys(): + self.log.warning('Filename is not listed in the offset list') + format_failure = True + if 'raoffset' not in offsets.keys(): + self.log.warning('raoffset is not listed in the offset list') + format_failure = True + if 'decoffset' not in offsets.keys(): + self.log.warning('decoffset is not listed in the offset list') + format_failure = True + if format_failure: + self.log.warning('Offset list does not have the correct format') + self.log.warning('No offsets are applied') + return None + for model in self.input_models: - print(model.meta.filename) file_check = model.meta.filename if file_check in offsets['filename']: - #print('found file', file_check) + ra = offsets['raoffset'] + dec = offsets['decoffset'] continue else: - print('In file from association not found in offset list') - print(offsets['filename']) + self.log.info('File in assocation is not found in offset list list %s', file_check) return None - return offsets - - + return offsets diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index ec606b2e1e..144804472c 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1316,7 +1316,21 @@ def setup_ifucube_wcs(self): input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] input_model = datamodels.open(input_file) - +# ________________________________________________________________________________ + # If offsets are provided. Pull in ra and dec offsets. + raoffset = 0.0 + decoffset = 0.0 + # pull out ra dec offset if it exists + if self.offsets is not None: + filename = input_model.meta.filename + index = self.offsets['filename'].index(filename) + raoffset = self.offsets['raoffset'][index] + decoffset = self.offsets['decoffset'][index] + log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", + raoffset, decoffset, filename) + raoffset = raoffset/3600.0 # convert to degress + decoffset = decoffset/3600.0 +# ________________________________________________________________________________ # Find the footprint of the image spectral_found = hasattr(input_model.meta.wcsinfo, 'spectral_region') spatial_found = hasattr(input_model.meta.wcsinfo, 's_region') @@ -1372,15 +1386,15 @@ def setup_ifucube_wcs(self): ca1, cb1, ca2, cb2, ca3, cb3, ca4, cb4, lmin, lmax = ch_corners # now append this model spatial and spectral corner - corner_a.append(ca1) - corner_a.append(ca2) - corner_a.append(ca3) - corner_a.append(ca4) + corner_a.append(ca1 + raoffset) + corner_a.append(ca2 + raoffset) + corner_a.append(ca3 + raoffset) + corner_a.append(ca4 + raoffset) - corner_b.append(cb1) - corner_b.append(cb2) - corner_b.append(cb3) - corner_b.append(cb4) + corner_b.append(cb1 + decoffset) + corner_b.append(cb2 + decoffset) + corner_b.append(cb3 + decoffset) + corner_b.append(cb4 + decoffset) lambda_min.append(lmin) lambda_max.append(lmax) @@ -1518,7 +1532,6 @@ def map_detector_to_outputframe(self, this_par1, y_det = None offsets = self.offsets - if self.instrument == 'MIRI': sky_result = self.map_miri_pixel_to_sky(input_model, this_par1, subtract_background, offsets) @@ -1713,6 +1726,8 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, needed for MIRI data input: datamodel input data model + offsets: dictionary + optional dictionary of ra and dec offsets to apply Returns ------- @@ -1730,10 +1745,12 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, if offsets is not None: filename = input_model.meta.filename index = offsets['filename'].index(filename) - raoffset = offsets['raoffset'][index]/3600.0 - decoffset = offsets['decoffset'][index]/3600.0 - - print('ra and dec offset to apply in degrees', raoffset, decoffset) + raoffset = offsets['raoffset'][index] + decoffset = offsets['decoffset'][index] + log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", + raoffset, decoffset, filename) + raoffset = raoffset/3600.0 # convert to degress + decoffset = decoffset/3600.0 # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been From 7d17e04fc82ba5b893fc845d9a4ba1f9d9f5253d Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 27 Aug 2024 09:15:30 -0700 Subject: [PATCH 03/37] updates --- docs/jwst/cube_build/arguments.rst | 32 +++++++++++++++++++++++++++ jwst/cube_build/cube_build_step.py | 1 + jwst/cube_build/ifu_cube.py | 35 +++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index a30a181b6c..af13f1e596 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -91,6 +91,13 @@ The following arguments control the size and sampling characteristics of the out ``nspax_y`` The odd integer number of spaxels to use in the y dimension of the tangent plane. +``offset_list = [string]`` + The string contains the name of the file holding ra and dec offsets to apply to each input file. This file + must be an asdf file and the it has a specific format. It is assumed the user has determined the ra and dec + offsets to apply to the data. For details on how to construct the format of the offset file see creating + :ref:`offsets` files. + + ``coord_system [string]`` The default IFU cubes are built on the ra-dec coordinate system (``coord_system=skyalign``). In these cubes north is up and east is left. There are two other coordinate systems an IFU cube can be built on: @@ -122,3 +129,28 @@ A parameter only used for investigating which detector pixels contributed to a c The string is the x,y,z value of the cube spaxel that is being investigated. The numbering starts counting at 0. To print information to the screeen about the x = 10, y = 20, z = 35 spaxel the parameter string value is '10 20 35'. + +.. _offsets: +The offset file is an ASDF formated file : `_ stands for "Advanced Scientific Data. For each input file in the spec3 assocation used to build the IFU cubes an ra and dec offset is provided. The offsets are in arc seconds. Below is an example of how to make an offset file. It is assumed the user has determined the +offsets to apply for each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary are `filenames`, `raoffset` and `decoffset`. The cube_building code is expects this dictionary to hold the information +for storing the file names and the associated ra and dec offsets. + +It is assumed there exists a list of files, ra and dec offsets that are feed to this routine. The ra and dec offsets +provided in arcseconds. The cube_building code will apply the ra offsets after multiplying by the +`num` is the number of files. + +import asdf +offsets = {} +offsets['filename'] = [] +offsets['raoffset'] = [] +offsets['decoffset'] = [] +for i in range(num): + + offsets['filename'].append(file[i]) + offsets['raoffset'].append(ra_center1[i]) + offsets['decoffset'].append(dec_center1[i]) + +af = asdf.AsdfFile({'offsets':offsets}) +af.write_to('offsets.asdf') + +The offset asdf filename can be any name, but it must have the `asdf` extension. diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 5726c4af6c..702b496090 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -582,6 +582,7 @@ def check_offset_list(self): for model in self.input_models: file_check = model.meta.filename + print(file_check) if file_check in offsets['filename']: ra = offsets['raoffset'] dec = offsets['decoffset'] diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 144804472c..a88129657e 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1861,6 +1861,21 @@ def map_nirspec_pixel_to_sky(self, input_model): x, y, ra, dec, lambda, slice_no """ + + # check if we have an ra and dec offset file + raoffset = 0.0 + decoffset = 0.0 + # pull out ra dec offset if it exists + if offsets is not None: + filename = input_model.meta.filename + index = offsets['filename'].index(filename) + raoffset = offsets['raoffset'][index] + decoffset = offsets['decoffset'][index] + log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", + raoffset, decoffset, filename) + raoffset = raoffset/3600.0 # convert to degress + decoffset = decoffset/3600.0 + # initialize the ra,dec, and wavelength arrays # we will loop over slice_nos and fill in values # the flag_det will be set when a slice_no pixel is filled in @@ -2009,19 +2024,19 @@ def map_nirspec_pixel_to_sky(self, input_model): valid_data = np.where(flag_det == 1) y, x = valid_data - ra = ra_det[valid_data] - dec = dec_det[valid_data] + ra = ra_det[valid_data] + raoffset + dec = dec_det[valid_data] + decoffset wave = lam_det[valid_data] slice_no = slice_det[valid_data] dwave = dwave_det[valid_data] - ra1 = ra1_det[valid_data] - ra2 = ra2_det[valid_data] - ra3 = ra3_det[valid_data] - ra4 = ra4_det[valid_data] - dec1 = dec1_det[valid_data] - dec2 = dec2_det[valid_data] - dec3 = dec3_det[valid_data] - dec4 = dec4_det[valid_data] + ra1 = ra1_det[valid_data] + raoffset + ra2 = ra2_det[valid_data] + raoffset + ra3 = ra3_det[valid_data] + raoffset + ra4 = ra4_det[valid_data] + raoffset + dec1 = dec1_det[valid_data] + decoffset + dec2 = dec2_det[valid_data] + decoffset + dec3 = dec3_det[valid_data] + decoffset + dec4 = dec4_det[valid_data] + decoffset corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) From aa5d546f820478fb1f84c550dc6e64f47ab19be5 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 27 Aug 2024 09:25:56 -0700 Subject: [PATCH 04/37] updates --- docs/jwst/cube_build/arguments.rst | 9 ++++++--- jwst/cube_build/cube_build_step.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index af13f1e596..6d718358b1 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -131,14 +131,17 @@ A parameter only used for investigating which detector pixels contributed to a c To print information to the screeen about the x = 10, y = 20, z = 35 spaxel the parameter string value is '10 20 35'. .. _offsets: -The offset file is an ASDF formated file : `_ stands for "Advanced Scientific Data. For each input file in the spec3 assocation used to build the IFU cubes an ra and dec offset is provided. The offsets are in arc seconds. Below is an example of how to make an offset file. It is assumed the user has determined the +The offset file is an ASDF formated file : `_ stands for "Advanced Scientific Data. For each +input file in the spec3 assocation used to build the IFU cubes, an ra and dec offset, in arc seconds, is provided. +Below is an example of how to make an ASDF offset file. It is assumed the user has determined the offsets to apply for each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary are `filenames`, `raoffset` and `decoffset`. The cube_building code is expects this dictionary to hold the information for storing the file names and the associated ra and dec offsets. It is assumed there exists a list of files, ra and dec offsets that are feed to this routine. The ra and dec offsets -provided in arcseconds. The cube_building code will apply the ra offsets after multiplying by the +provided in arcseconds. The cube_building code will apply the ra offsets after multiplying by the cos(crval2), where crval2 is the +declination center of the IFU cube. `num` is the number of files. - +y import asdf offsets = {} offsets['filename'] = [] diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 702b496090..5726c4af6c 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -582,7 +582,6 @@ def check_offset_list(self): for model in self.input_models: file_check = model.meta.filename - print(file_check) if file_check in offsets['filename']: ra = offsets['raoffset'] dec = offsets['decoffset'] From c121c1fc8ac54ed79c94fc61cff6ed8abbae726c Mon Sep 17 00:00:00 2001 From: jemorrison Date: Wed, 28 Aug 2024 17:15:05 -0700 Subject: [PATCH 05/37] update for cos(dec) --- docs/jwst/cube_build/arguments.rst | 19 ++++++++++--------- jwst/cube_build/ifu_cube.py | 26 +++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 6d718358b1..ab329aa6ee 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -131,24 +131,25 @@ A parameter only used for investigating which detector pixels contributed to a c To print information to the screeen about the x = 10, y = 20, z = 35 spaxel the parameter string value is '10 20 35'. .. _offsets: -The offset file is an ASDF formated file : `_ stands for "Advanced Scientific Data. For each -input file in the spec3 assocation used to build the IFU cubes, an ra and dec offset, in arc seconds, is provided. +The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each +input file in the spec3 assocation used to build the IFU cubes, there is a corresponding right ascension and declination offset, +given arc seconds. Below is an example of how to make an ASDF offset file. It is assumed the user has determined the -offsets to apply for each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary are `filenames`, `raoffset` and `decoffset`. The cube_building code is expects this dictionary to hold the information -for storing the file names and the associated ra and dec offsets. +offsets to apply the data in each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary +are `filenames`, `raoffset` and `decoffset`. The IFU cube building code expects this dictionary to hold the information +for storing the file names and the associated ra and dec offsets. The file names should not contain the directory path. -It is assumed there exists a list of files, ra and dec offsets that are feed to this routine. The ra and dec offsets -provided in arcseconds. The cube_building code will apply the ra offsets after multiplying by the cos(crval2), where crval2 is the +It is assumed there exists a list of files, ra and dec offsets that are feed to this method. The ra and dec offsets +provided in arcseconds. The cube building code will apply the ra offsets after dividing by cos(crval2), where crval2 is the declination center of the IFU cube. -`num` is the number of files. -y +Below `num` is the number of files. + import asdf offsets = {} offsets['filename'] = [] offsets['raoffset'] = [] offsets['decoffset'] = [] for i in range(num): - offsets['filename'].append(file[i]) offsets['raoffset'].append(ra_center1[i]) offsets['decoffset'].append(dec_center1[i]) diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index a88129657e..6755e9e2e4 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -114,7 +114,8 @@ def __init__(self, self.naxis3 = None self.cdelt3_normal = None self.rot_angle = None # rotation angle between Ra-Dec and IFU local instrument plane - + self.median_dec = None + self.a_min = 0 self.a_max = 0 self.b_min = 0 @@ -1234,6 +1235,7 @@ def setup_ifucube_wcs(self): # _____________________________________________________________________________ self.cdelt1 = self.spatial_size self.cdelt2 = self.spatial_size + deg2rad = math.pi / 180.0 if self.linear_wavelength: self.cdelt3 = self.spectral_size @@ -1310,6 +1312,25 @@ def setup_ifucube_wcs(self): log.debug(f'Working on data from {this_a}, {this_b}') n = len(self.master_table.FileMap[self.instrument][this_a][this_b]) log.debug('number of files %d', n) + + # find the median center declination if we have an offset file + if self.offsets is not None: + decs = [] + for k in range(n): + input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] + input_model = datamodels.open(input_file) + spatial_box = input_model.meta.wcsinfo.s_region + s = spatial_box.split(' ') + cb1 = float(s[4]) + cb2 = float(s[6]) + cb3 = float(s[8]) + cb4 = float(s[10]) + m = (cb1 + cb2 + cb3 + cb4)/4 + decs.append(m) + + self.median_dec = np.nanmedian(decs) + print('Median declination ', self.median_dec) + for k in range(n): lmin = 0.0 lmax = 0.0 @@ -1329,6 +1350,7 @@ def setup_ifucube_wcs(self): log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", raoffset, decoffset, filename) raoffset = raoffset/3600.0 # convert to degress + raoffset = raoffset /np.cos(self.median_dec *deg2rad) decoffset = decoffset/3600.0 # ________________________________________________________________________________ # Find the footprint of the image @@ -1738,6 +1760,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, slice_no = None # Slice number dwave = None corner_coord = None + deg2rad = math.pi / 180.0 raoffset = 0.0 decoffset = 0.0 @@ -1750,6 +1773,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", raoffset, decoffset, filename) raoffset = raoffset/3600.0 # convert to degress + raoffset = raoffset /np.cos(self.median_dec *deg2rad) decoffset = decoffset/3600.0 # check if background sky matching as been done in mrs_imatch step From 44f416a68eab891ea989b6f542f2a3b633a6671c Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 29 Aug 2024 09:47:44 -0700 Subject: [PATCH 06/37] update --- docs/jwst/cube_build/arguments.rst | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index ab329aa6ee..71810e5b76 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -131,6 +131,10 @@ A parameter only used for investigating which detector pixels contributed to a c To print information to the screeen about the x = 10, y = 20, z = 35 spaxel the parameter string value is '10 20 35'. .. _offsets: + +Creating an offset file +----------------------- + The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each input file in the spec3 assocation used to build the IFU cubes, there is a corresponding right ascension and declination offset, given arc seconds. @@ -144,17 +148,19 @@ provided in arcseconds. The cube building code will apply the ra offsets after d declination center of the IFU cube. Below `num` is the number of files. -import asdf -offsets = {} -offsets['filename'] = [] -offsets['raoffset'] = [] -offsets['decoffset'] = [] -for i in range(num): - offsets['filename'].append(file[i]) - offsets['raoffset'].append(ra_center1[i]) - offsets['decoffset'].append(dec_center1[i]) - -af = asdf.AsdfFile({'offsets':offsets}) -af.write_to('offsets.asdf') + +.. code-block:: python + + import asdf + offsets = {} + offsets['filename'] = [] + offsets['raoffset'] = [] + offsets['decoffset'] = [] + for i in range(num): + offsets['filename'].append(file[i]) + offsets['raoffset'].append(ra_center1[i]) + offsets['decoffset'].append(dec_center1[i]) + af = asdf.AsdfFile({'offsets':offsets}) + af.write_to('offsets.asdf') The offset asdf filename can be any name, but it must have the `asdf` extension. From e1fcac64db1352c0cb9d72c0d00f8886ce0ab80b Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 29 Aug 2024 10:03:26 -0700 Subject: [PATCH 07/37] fix docs --- docs/jwst/cube_build/arguments.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 71810e5b76..3f318941db 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -91,11 +91,11 @@ The following arguments control the size and sampling characteristics of the out ``nspax_y`` The odd integer number of spaxels to use in the y dimension of the tangent plane. -``offset_list = [string]`` +``offset_list [string]`` The string contains the name of the file holding ra and dec offsets to apply to each input file. This file must be an asdf file and the it has a specific format. It is assumed the user has determined the ra and dec - offsets to apply to the data. For details on how to construct the format of the offset file see creating - :ref:`offsets` files. + offsets to apply to the data. For details on how to construct the format of the offset file see + :ref:`offsets`. ``coord_system [string]`` @@ -136,15 +136,14 @@ Creating an offset file ----------------------- The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each -input file in the spec3 assocation used to build the IFU cubes, there is a corresponding right ascension and declination offset, -given arc seconds. +input file in the spec3 assocation used to build the IFU cubes, the offset files needs to have a corresponding right ascension and declination offset given arc seconds. Below is an example of how to make an ASDF offset file. It is assumed the user has determined the -offsets to apply the data in each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary +offsets to apply to the data in each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary are `filenames`, `raoffset` and `decoffset`. The IFU cube building code expects this dictionary to hold the information for storing the file names and the associated ra and dec offsets. The file names should not contain the directory path. -It is assumed there exists a list of files, ra and dec offsets that are feed to this method. The ra and dec offsets -provided in arcseconds. The cube building code will apply the ra offsets after dividing by cos(crval2), where crval2 is the +It is assumed there exists a list of files, ra and dec offsets that are feed to this method. The ra and dec offsets need to be +in arcseconds. The cube building code will apply the ra offsets after dividing by cos(crval2), where crval2 is the declination center of the IFU cube. Below `num` is the number of files. From 984c0c7a2c39c33847890e90d71e995bba6b3d2d Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 29 Aug 2024 10:05:12 -0700 Subject: [PATCH 08/37] update docs --- docs/jwst/cube_build/arguments.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 3f318941db..c7c06b0746 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -144,7 +144,7 @@ for storing the file names and the associated ra and dec offsets. The file names It is assumed there exists a list of files, ra and dec offsets that are feed to this method. The ra and dec offsets need to be in arcseconds. The cube building code will apply the ra offsets after dividing by cos(crval2), where crval2 is the -declination center of the IFU cube. +declination center of the IFU cube. The offset asdf filename can be any name, but it must have the `asdf` extension. Below `num` is the number of files. @@ -162,4 +162,4 @@ Below `num` is the number of files. af = asdf.AsdfFile({'offsets':offsets}) af.write_to('offsets.asdf') -The offset asdf filename can be any name, but it must have the `asdf` extension. + From afec305c902697c474f4004c981abd03fdd670b4 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 5 Sep 2024 06:48:53 -0700 Subject: [PATCH 09/37] added schema to cube_build --- jwst/cube_build/cube_build_step.py | 17 ++++++++------- jwst/cube_build/ifu_cube.py | 25 +++++++++++----------- jwst/cube_build/ifuoffset.schema.yaml | 30 +++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 jwst/cube_build/ifuoffset.schema.yaml diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 5726c4af6c..3af0d8d6ea 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -41,7 +41,7 @@ class CubeBuildStep (Step): spec = """ channel = option('1','2','3','4','all',default='all') # Channel - band = option('short','medium','long','short-medium','short-long','medium-short', \ + band = option('short','medium','long','short-medium','short-long','medium-short', 'medium-long', 'long-short', 'long-medium','all',default='all') # Band grating = option('prism','g140m','g140h','g235m','g235h','g395m','g395h','all',default='all') # Grating filter = option('clear','f100lp','f070lp','f170lp','f290lp','all',default='all') # Filter @@ -65,7 +65,7 @@ class CubeBuildStep (Step): search_output_file = boolean(default=false) output_use_model = boolean(default=true) # Use filenames in the output models suffix = string(default='s3d') - offset_list = string(default=None) + offset_file = string(default=None) debug_spaxel = string(default='-1 -1 -1') # Default not used """ @@ -245,8 +245,8 @@ def process(self, input): # Each row in the offset list contain a filename, ra offset and dec offset. # The offset list is an asdf file. self.offsets = None - if self.offset_list is not None: - offsets = self.check_offset_list() + if self.offset_file is not None: + offsets = self.check_offset_file() if offsets is not None: self.offsets = offsets # ________________________________________________________________________________ @@ -546,7 +546,8 @@ def read_user_input(self): self.pars_input['grating'] = list(set(self.pars_input['grating'])) # ________________________________________________________________________________ - def check_offset_list(self): + + def check_offset_file(self): """Read in an optional ra and dec offsets for each file. Summary @@ -559,9 +560,11 @@ def check_offset_list(self): """ - check_asdf = asdf.util.get_file_type(asdf.generic_io.get_file(self.offset_list)) + af = asdf.open(self.offset_file, custom_schema = 'ifuoffset_schema.yaml') + + check_asdf = asdf.util.get_file_type(asdf.generic_io.get_file(self.offset_file)) if check_asdf == asdf.util.FileType.ASDF: - with asdf.open(self.offset_list) as af: + with asdf.open(self.offset_file) as af: offsets = af.tree['offsets'] format_failure = False diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 6755e9e2e4..ad27bad270 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1329,7 +1329,15 @@ def setup_ifucube_wcs(self): decs.append(m) self.median_dec = np.nanmedian(decs) - print('Median declination ', self.median_dec) + # fold in the median_dec information into ra offset + noffsets = len(self.offsets['raoffset']) + # convert ra and dec offsets to degrees and adjust ra offset for cos(dec) + for im in range(noffsets): + self.offsets['raoffset'][im] = (self.offsets['raoffset'][im]/3600.0)/np.cos(self.median_dec*deg2rad) + self.offsets['decoffset'][im] = self.offsets['decoffset'][im]/3600.0 + + print(self.offsets['raoffset']) + print(self.offsets['decoffset']) for k in range(n): lmin = 0.0 @@ -1347,11 +1355,6 @@ def setup_ifucube_wcs(self): index = self.offsets['filename'].index(filename) raoffset = self.offsets['raoffset'][index] decoffset = self.offsets['decoffset'][index] - log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", - raoffset, decoffset, filename) - raoffset = raoffset/3600.0 # convert to degress - raoffset = raoffset /np.cos(self.median_dec *deg2rad) - decoffset = decoffset/3600.0 # ________________________________________________________________________________ # Find the footprint of the image spectral_found = hasattr(input_model.meta.wcsinfo, 'spectral_region') @@ -1770,11 +1773,9 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, index = offsets['filename'].index(filename) raoffset = offsets['raoffset'][index] decoffset = offsets['decoffset'][index] - log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", - raoffset, decoffset, filename) - raoffset = raoffset/3600.0 # convert to degress - raoffset = raoffset /np.cos(self.median_dec *deg2rad) - decoffset = decoffset/3600.0 + log.info("Ra and dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", + raoffset*3600.0*np.cos(self.median_dec*deg2rad), + decoffset*3600.0, filename) # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been @@ -1897,8 +1898,6 @@ def map_nirspec_pixel_to_sky(self, input_model): decoffset = offsets['decoffset'][index] log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", raoffset, decoffset, filename) - raoffset = raoffset/3600.0 # convert to degress - decoffset = decoffset/3600.0 # initialize the ra,dec, and wavelength arrays # we will loop over slice_nos and fill in values diff --git a/jwst/cube_build/ifuoffset.schema.yaml b/jwst/cube_build/ifuoffset.schema.yaml new file mode 100644 index 0000000000..c003afcb64 --- /dev/null +++ b/jwst/cube_build/ifuoffset.schema.yaml @@ -0,0 +1,30 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" +id: "http://stsci.edu/schemas/yaml-schema/ifuoffset.schema" + +title: IFUoffset reference file model +type: object +properties: + meta: + type: object + properties: + units: + description: Units of the ra and dec offset values. + anyOf: + - type: string + - $ref: http://stsci.edu/schemas/asdf/unit/unit-1.0.0 + offsets: + description: dictionary defining offsets values + type: object + items: + type: object + properties: + filename: + type: string + raoffset: + type: number + decoffset: + type: number + +required: [meta, offsets] From 8e464b24f211599200914c63160dab0fa4fb1010 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 5 Sep 2024 07:13:21 -0700 Subject: [PATCH 10/37] trying to include offset yaml file --- jwst/cube_build/cube_build_step.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 3af0d8d6ea..79d69c765e 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -7,6 +7,7 @@ from jwst.lib.pipe_utils import match_nans_and_flags from . import cube_build +from . import ifuoffset.schema.yaml from . import ifu_cube from . import data_types import asdf @@ -41,7 +42,7 @@ class CubeBuildStep (Step): spec = """ channel = option('1','2','3','4','all',default='all') # Channel - band = option('short','medium','long','short-medium','short-long','medium-short', + band = option('short','medium','long','short-medium','short-long','medium-short', \ 'medium-long', 'long-short', 'long-medium','all',default='all') # Band grating = option('prism','g140m','g140h','g235m','g235h','g395m','g395h','all',default='all') # Grating filter = option('clear','f100lp','f070lp','f170lp','f290lp','all',default='all') # Filter From bb351f8fbb4c8110343ebb7288a35db9b42b0247 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Sun, 8 Sep 2024 13:46:47 -0700 Subject: [PATCH 11/37] corrected using a local schema file --- docs/jwst/cube_build/arguments.rst | 24 ++++++++----- jwst/cube_build/cube_build_step.py | 49 ++++++++++++--------------- jwst/cube_build/ifu_cube.py | 3 -- jwst/cube_build/ifuoffset.schema.yaml | 43 ++++++++++++----------- 4 files changed, 57 insertions(+), 62 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index c7c06b0746..4cfcc95ed1 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -151,15 +151,21 @@ Below `num` is the number of files. .. code-block:: python import asdf - offsets = {} - offsets['filename'] = [] - offsets['raoffset'] = [] - offsets['decoffset'] = [] + filename = [] + raoffset = [] + decoffset = [] for i in range(num): - offsets['filename'].append(file[i]) - offsets['raoffset'].append(ra_center1[i]) - offsets['decoffset'].append(dec_center1[i]) - af = asdf.AsdfFile({'offsets':offsets}) - af.write_to('offsets.asdf') + filename.append(file[i]) + raoffset.append(ra_center1[i]) + decoffset.append(dec_center1[i]) + + tree = { + "units": str(u.arcsec), + "filename": filename, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + af.write_to(input_dir + 'offsets.asdf') diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 79d69c765e..a9e121efc5 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -2,17 +2,16 @@ """ import time - from jwst.datamodels import ModelContainer from jwst.lib.pipe_utils import match_nans_and_flags from . import cube_build -from . import ifuoffset.schema.yaml from . import ifu_cube from . import data_types import asdf from ..assign_wcs.util import update_s_region_keyword from ..stpipe import Step, record_step_status +from pathlib import Path __all__ = ["CubeBuildStep"] @@ -547,7 +546,6 @@ def read_user_input(self): self.pars_input['grating'] = list(set(self.pars_input['grating'])) # ________________________________________________________________________________ - def check_offset_file(self): """Read in an optional ra and dec offsets for each file. @@ -561,36 +559,31 @@ def check_offset_file(self): """ - af = asdf.open(self.offset_file, custom_schema = 'ifuoffset_schema.yaml') - - check_asdf = asdf.util.get_file_type(asdf.generic_io.get_file(self.offset_file)) - if check_asdf == asdf.util.FileType.ASDF: - with asdf.open(self.offset_file) as af: - offsets = af.tree['offsets'] - - format_failure = False - # Currently the offset list has to have the following keys: filename, raoffset, decoffset - if 'filename' not in offsets.keys(): - self.log.warning('Filename is not listed in the offset list') - format_failure = True - if 'raoffset' not in offsets.keys(): - self.log.warning('raoffset is not listed in the offset list') - format_failure = True - if 'decoffset' not in offsets.keys(): - self.log.warning('decoffset is not listed in the offset list') - format_failure = True - if format_failure: - self.log.warning('Offset list does not have the correct format') - self.log.warning('No offsets are applied') + # validate the offset file using the schema file + DATA_PATH = Path(__file__).parent + af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') + + offset_filename = af['filename'] + offset_ra = af['raoffset'] + offset_dec = af['decoffset'] + offset_unit = af['units'] + + if offset_unit != 'arcsec': + self.log.error('Provide the offset units in units of arcsec ') + self.log.error('Turning off adjusting by offsets ') return None for model in self.input_models: file_check = model.meta.filename - if file_check in offsets['filename']: - ra = offsets['raoffset'] - dec = offsets['decoffset'] + if file_check in offset_filename: continue else: - self.log.info('File in assocation is not found in offset list list %s', file_check) + self.log.error('File in assocation is not found in offset list list %s', file_check) + self.log.error('Turning off adjusting by offsets') return None + offsets = {} + offsets['filename'] = offset_filename + offsets['raoffset'] = offset_ra + offsets['decoffset'] = offset_dec + return offsets diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index ad27bad270..1033c7a26c 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1335,9 +1335,6 @@ def setup_ifucube_wcs(self): for im in range(noffsets): self.offsets['raoffset'][im] = (self.offsets['raoffset'][im]/3600.0)/np.cos(self.median_dec*deg2rad) self.offsets['decoffset'][im] = self.offsets['decoffset'][im]/3600.0 - - print(self.offsets['raoffset']) - print(self.offsets['decoffset']) for k in range(n): lmin = 0.0 diff --git a/jwst/cube_build/ifuoffset.schema.yaml b/jwst/cube_build/ifuoffset.schema.yaml index c003afcb64..c2c7d40c6a 100644 --- a/jwst/cube_build/ifuoffset.schema.yaml +++ b/jwst/cube_build/ifuoffset.schema.yaml @@ -2,29 +2,28 @@ --- $schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" id: "http://stsci.edu/schemas/yaml-schema/ifuoffset.schema" - title: IFUoffset reference file model type: object properties: - meta: - type: object - properties: - units: - description: Units of the ra and dec offset values. - anyOf: - - type: string - - $ref: http://stsci.edu/schemas/asdf/unit/unit-1.0.0 - offsets: - description: dictionary defining offsets values - type: object - items: - type: object - properties: - filename: - type: string - raoffset: - type: number - decoffset: - type: number + units: + description: Units of the ra and dec offset values. + anyOf: + - type: string + - $ref: http://stsci.edu/schemas/asdf/unit/unit-1.0.0 + filename: + description: list of filenames + type: array + items: + type: string + raoffset: + description: list of ra offsets + type: array + items: + type: number + decoffset: + descrition: list of dec offsets + type: array + items: + type: number -required: [meta, offsets] +required: [filename, raoffset, decoffset, units] From e20bd6a4adf62734d209a6037a49d8db07ea96bc Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 9 Sep 2024 07:00:56 -0700 Subject: [PATCH 12/37] fix conflict --- jwst/cube_build/cube_build_step.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index a9e121efc5..4b2d6133c3 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -4,7 +4,6 @@ import time from jwst.datamodels import ModelContainer from jwst.lib.pipe_utils import match_nans_and_flags - from . import cube_build from . import ifu_cube from . import data_types @@ -237,7 +236,6 @@ def process(self, input): self.output_type = 'channel' self.pars_input['output_type'] = self.output_type self.log.info(f'Setting output type to: {self.output_type}') - # ________________________________________________________________________________ # If an offset file is provided do some basic checks on the file and its contents. # The offset list contains a matching list to the files in the association From 798a5f71c8dfb30abb24d5d6830cdb2c319464bc Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 9 Sep 2024 11:15:44 -0700 Subject: [PATCH 13/37] added more checks for offset file --- jwst/cube_build/cube_build_step.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 4b2d6133c3..069af6bdbc 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -559,7 +559,12 @@ def check_offset_file(self): # validate the offset file using the schema file DATA_PATH = Path(__file__).parent - af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') + try: + af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') + except + self.log.error('Validation Error for offset file') + self.log.error('Turning off adjusting by offsets') + return None offset_filename = af['filename'] offset_ra = af['raoffset'] From a503c29af04e7782f4eaa17fb7469230abef0721 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 9 Sep 2024 12:53:52 -0700 Subject: [PATCH 14/37] fix typo --- jwst/cube_build/cube_build_step.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 069af6bdbc..d5f36cdefb 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -561,7 +561,7 @@ def check_offset_file(self): DATA_PATH = Path(__file__).parent try: af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') - except + except: self.log.error('Validation Error for offset file') self.log.error('Turning off adjusting by offsets') return None From 0e7cc1b7882f1848229d7d7b7aa4bc9188b893d2 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 9 Sep 2024 14:43:17 -0700 Subject: [PATCH 15/37] attempt on unit test --- jwst/cube_build/tests/test_configuration.py | 78 ++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/jwst/cube_build/tests/test_configuration.py b/jwst/cube_build/tests/test_configuration.py index e3783c1ca8..9619219e28 100644 --- a/jwst/cube_build/tests/test_configuration.py +++ b/jwst/cube_build/tests/test_configuration.py @@ -3,12 +3,15 @@ """ import pytest - +import asdf from stdatamodels.jwst import datamodels +import astropy.units as u +from jwst.cube_build import CubeBuildStep from jwst.cube_build import cube_build from jwst.cube_build import file_table + wcsinfo = { 'dec_ref': -0.00244536159612126, 'ra_ref': -0.00205553321270217, @@ -100,6 +103,23 @@ } +@pytest.fixture(scope='function') +def offset_file(): + """ Generate a offset file """ + + filename = ['test1.fits', 'test2.fits'] + raoffset = [0.0, 0.1] + decoffset = [0.0, 0.15] + tree = { + "units": str(u.arcsec), + "filename": filename, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + return af + + @pytest.fixture(scope='function') def miri_ifushort_short(): """ Generate a IFU image """ @@ -113,6 +133,30 @@ def miri_ifushort_short(): return input_model +@pytest.fixture(scope='function') +def miri_ifushort_short_2files(): + """ Generate a IFU image """ + + input_model1 = datamodels.IFUImageModel() + input_model1.meta.wcsinfo._instance.update(wcsinfo) + input_model1.meta.instrument._instance.update(mirifushort_short) + input_model1.meta.observation._instance.update(observation) + input_model1.meta.subarray._instance.update(subarray) + input_model1.meta.cal_step.assign_wcs = 'COMPLETE' + + input_model2 = datamodels.IFUImageModel() + input_model2.meta.wcsinfo._instance.update(wcsinfo) + input_model2.meta.instrument._instance.update(mirifushort_short) + input_model2.meta.observation._instance.update(observation) + input_model2.meta.subarray._instance.update(subarray) + input_model2.meta.cal_step.assign_wcs = 'COMPLETE' + + input_models = [] + input_models.append(input_model1) + input_models.append(input_model1) + return input_models + + @pytest.fixture(scope='function') def miri_full_coverage(): """ Generate a IFU images SHORT, LONG for all three bands """ @@ -467,3 +511,35 @@ def test_calspec3_config_nirspec_multi(tmp_cwd, nirspec_medium_coverage): assert cube_pars['1']['par1'] == ['g140m', 'g235m'] assert cube_pars['1']['par2'] == ['f100lp', 'f170lp'] + + +def test_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): + """ Test validation of the offset configuration""" + + pars_input = {} + pars_input['channel'] = [] + pars_input['subchannel'] = [] + pars_input['filter'] = [] + pars_input['grating'] = [] + output_type = 'band' + weighting = 'drizzle' + par_filename = 'None' + + pars = { + 'channel': pars_input['channel'], + 'subchannel': pars_input['subchannel'], + 'grating': pars_input['grating'], + 'filter': pars_input['filter'], + 'weighting': weighting, + 'output_type': output_type} + + cubeinfo = cube_build.CubeData( + miri_ifushort_short_2files, + par_filename, + **pars) + + offsets = CubeBuildStep.check_offset_file(cubeinfo.input_models) + + + # want to test that offsets is None with it fails or Dictionary when it works + From 9446cadfcaff919842ec5801feedea1462555302 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Wed, 11 Sep 2024 19:05:52 -0700 Subject: [PATCH 16/37] added a unit test --- jwst/cube_build/cube_build_step.py | 2 + jwst/cube_build/tests/test_configuration.py | 87 ++++++++++++++------- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index d5f36cdefb..7a977d15b0 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -559,6 +559,7 @@ def check_offset_file(self): # validate the offset file using the schema file DATA_PATH = Path(__file__).parent + try: af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') except: @@ -589,4 +590,5 @@ def check_offset_file(self): offsets['raoffset'] = offset_ra offsets['decoffset'] = offset_dec + af.close() return offsets diff --git a/jwst/cube_build/tests/test_configuration.py b/jwst/cube_build/tests/test_configuration.py index 9619219e28..d155b7ac82 100644 --- a/jwst/cube_build/tests/test_configuration.py +++ b/jwst/cube_build/tests/test_configuration.py @@ -103,21 +103,46 @@ } -@pytest.fixture(scope='function') -def offset_file(): +@pytest.fixture(scope='module') +def offset_file(tmp_path_factory): """ Generate a offset file """ - filename = ['test1.fits', 'test2.fits'] + filename = tmp_path_factory.mktemp('offset') + filename = filename / 'offset.asdf' + + testfile = ['test1.fits', 'test2.fits'] raoffset = [0.0, 0.1] decoffset = [0.0, 0.15] tree = { "units": str(u.arcsec), - "filename": filename, + "filename": testfile, "raoffset": raoffset, "decoffset": decoffset } af = asdf.AsdfFile(tree) - return af + af.write_to(filename) + return filename + + +@pytest.fixture(scope='module') +def offset_file_arcmin(tmp_path_factory): + """ Generate a offset file with units = arcmin """ + + filename = tmp_path_factory.mktemp('offset_arcmin') + filename = filename / 'offset_arcmin.asdf' + + testfile = ['test1.fits', 'test2.fits'] + raoffset = [0.0, 0.1] + decoffset = [0.0, 0.15] + tree = { + "units": str(u.arcmin), + "filename": testfile, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + af.write_to(filename) + return filename @pytest.fixture(scope='function') @@ -143,6 +168,7 @@ def miri_ifushort_short_2files(): input_model1.meta.observation._instance.update(observation) input_model1.meta.subarray._instance.update(subarray) input_model1.meta.cal_step.assign_wcs = 'COMPLETE' + input_model1.meta.filename = 'test1.fits' input_model2 = datamodels.IFUImageModel() input_model2.meta.wcsinfo._instance.update(wcsinfo) @@ -150,10 +176,11 @@ def miri_ifushort_short_2files(): input_model2.meta.observation._instance.update(observation) input_model2.meta.subarray._instance.update(subarray) input_model2.meta.cal_step.assign_wcs = 'COMPLETE' + input_model2.meta.filename = 'test2.fits' input_models = [] input_models.append(input_model1) - input_models.append(input_model1) + input_models.append(input_model2) return input_models @@ -516,30 +543,36 @@ def test_calspec3_config_nirspec_multi(tmp_cwd, nirspec_medium_coverage): def test_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): """ Test validation of the offset configuration""" - pars_input = {} - pars_input['channel'] = [] - pars_input['subchannel'] = [] - pars_input['filter'] = [] - pars_input['grating'] = [] - output_type = 'band' - weighting = 'drizzle' - par_filename = 'None' + # first test that it is a valid asdf file and has what is needed + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + step.offset_file = offset_file + offsets = step.check_offset_file() + assert isinstance(offsets, dict) - pars = { - 'channel': pars_input['channel'], - 'subchannel': pars_input['subchannel'], - 'grating': pars_input['grating'], - 'filter': pars_input['filter'], - 'weighting': weighting, - 'output_type': output_type} +def test2_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): + """ Test validation of the offset configuration""" - cubeinfo = cube_build.CubeData( - miri_ifushort_short_2files, - par_filename, - **pars) + # Test changing one of the filenames so it is not in the list given + # in the offset_file + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + miri_ifushort_short_2files[0].meta.filename = 'test3.fits' + step.offset_file = offset_file + offsets = step.check_offset_file() + assert offsets is None - offsets = CubeBuildStep.check_offset_file(cubeinfo.input_models) +def test_offset_file_config2(tmp_cwd, miri_ifushort_short_2files, offset_file_arcmin): + """ Test validation of the offset configuration""" - # want to test that offsets is None with it fails or Dictionary when it works + # test is the if the user set the units to arcmins + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + step.offset_file = offset_file_arcmin + offsets = step.check_offset_file() + assert offsets is None From 0fd9af7c641021624a9e5ece29ef9789c7045343 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 12 Sep 2024 06:29:54 -0700 Subject: [PATCH 17/37] fix api for nirspec --- jwst/cube_build/ifu_cube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 1033c7a26c..9545cf4451 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1866,7 +1866,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, return sky_result # ______________________________________________________________________ - def map_nirspec_pixel_to_sky(self, input_model): + def map_nirspec_pixel_to_sky(self, input_model, offsets): """Loop over a file and map the detector pixels to the output cube From 407c21653420fb595ef4dc41e75f43b2c196dd4d Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 12 Sep 2024 10:35:35 -0700 Subject: [PATCH 18/37] added closing asdf file --- jwst/cube_build/cube_build_step.py | 3 +++ jwst/cube_build/tests/test_configuration.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 7a977d15b0..08f00d2d52 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -567,6 +567,7 @@ def check_offset_file(self): self.log.error('Turning off adjusting by offsets') return None + offset_filename = af['filename'] offset_ra = af['raoffset'] offset_dec = af['decoffset'] @@ -575,6 +576,7 @@ def check_offset_file(self): if offset_unit != 'arcsec': self.log.error('Provide the offset units in units of arcsec ') self.log.error('Turning off adjusting by offsets ') + af.close() return None for model in self.input_models: @@ -584,6 +586,7 @@ def check_offset_file(self): else: self.log.error('File in assocation is not found in offset list list %s', file_check) self.log.error('Turning off adjusting by offsets') + af.close() return None offsets = {} offsets['filename'] = offset_filename diff --git a/jwst/cube_build/tests/test_configuration.py b/jwst/cube_build/tests/test_configuration.py index d155b7ac82..0ef5ab1e2e 100644 --- a/jwst/cube_build/tests/test_configuration.py +++ b/jwst/cube_build/tests/test_configuration.py @@ -121,6 +121,7 @@ def offset_file(tmp_path_factory): } af = asdf.AsdfFile(tree) af.write_to(filename) + af.close() return filename @@ -128,7 +129,7 @@ def offset_file(tmp_path_factory): def offset_file_arcmin(tmp_path_factory): """ Generate a offset file with units = arcmin """ - filename = tmp_path_factory.mktemp('offset_arcmin') + filename = tmp_path_factory.mktemp('offset') filename = filename / 'offset_arcmin.asdf' testfile = ['test1.fits', 'test2.fits'] From a27cdaaeb2e1a4ab5c49d6a6b2a94a0d8e757caf Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 12 Sep 2024 13:12:39 -0700 Subject: [PATCH 19/37] Update Change Log --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index db515eadf3..32b9ab706d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -82,6 +82,8 @@ cube_build - Replaced deep copies of NIRSpec WCS objects within most loops. [#8793] +- Allow the user to provide ra and dec shifts to apply for each file to fine tune the WCS. [#JP-3364] + datamodels ---------- From c3dbb3de750fae436919652b58d47bfb598b8295 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 12 Sep 2024 17:28:36 -0700 Subject: [PATCH 20/37] updates to docs --- CHANGES.rst | 3 ++- docs/jwst/cube_build/arguments.rst | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 32b9ab706d..4e8212be57 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -82,7 +82,8 @@ cube_build - Replaced deep copies of NIRSpec WCS objects within most loops. [#8793] -- Allow the user to provide ra and dec shifts to apply for each file to fine tune the WCS. [#JP-3364] +- Allow the user to provide ra and dec shifts to apply for each file to fine + tune the WCS. [#JP-3364] datamodels ---------- diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 4cfcc95ed1..633cdd68f8 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -91,7 +91,7 @@ The following arguments control the size and sampling characteristics of the out ``nspax_y`` The odd integer number of spaxels to use in the y dimension of the tangent plane. -``offset_list [string]`` +``offset_file [string]`` The string contains the name of the file holding ra and dec offsets to apply to each input file. This file must be an asdf file and the it has a specific format. It is assumed the user has determined the ra and dec offsets to apply to the data. For details on how to construct the format of the offset file see @@ -138,19 +138,19 @@ Creating an offset file The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each input file in the spec3 assocation used to build the IFU cubes, the offset files needs to have a corresponding right ascension and declination offset given arc seconds. Below is an example of how to make an ASDF offset file. It is assumed the user has determined the -offsets to apply to the data in each file. The offsets are stored in a python dictionary, `offsets`. The items of this dictionary -are `filenames`, `raoffset` and `decoffset`. The IFU cube building code expects this dictionary to hold the information -for storing the file names and the associated ra and dec offsets. The file names should not contain the directory path. +offsets to apply to the data in each file. The offsets information is stored in three lists: + `filenames`, `raoffset` and `decoffset`. The units of the ra and dec offsets + are required to be in the offset set file and only the unit, `arcsec`, is allowed. The file names should +not contain the directory path. The offset asdf filename can be any name, but it must have the `asdf` extension. -It is assumed there exists a list of files, ra and dec offsets that are feed to this method. The ra and dec offsets need to be -in arcseconds. The cube building code will apply the ra offsets after dividing by cos(crval2), where crval2 is the -declination center of the IFU cube. The offset asdf filename can be any name, but it must have the `asdf` extension. Below `num` is the number of files. .. code-block:: python import asdf + import astropy.units as u + filename = [] raoffset = [] decoffset = [] @@ -166,6 +166,7 @@ Below `num` is the number of files. "decoffset": decoffset } af = asdf.AsdfFile(tree) - af.write_to(input_dir + 'offsets.asdf') + af.write_to(input_dir + 'offsets.asdf') + af.close() From d9183228d259fa9628f240c589a7e166b714fcf5 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Fri, 13 Sep 2024 08:28:59 -0700 Subject: [PATCH 21/37] update docs --- docs/jwst/cube_build/arguments.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 633cdd68f8..abd08926b1 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -136,13 +136,12 @@ Creating an offset file ----------------------- The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each -input file in the spec3 assocation used to build the IFU cubes, the offset files needs to have a corresponding right ascension and declination offset given arc seconds. +input file in the spec3 assocation used to build the IFU cubes, the offset files needs to have a corresponding right ascension and declination offset given arc seconds. Below is an example of how to make an ASDF offset file. It is assumed the user has determined the offsets to apply to the data in each file. The offsets information is stored in three lists: - `filenames`, `raoffset` and `decoffset`. The units of the ra and dec offsets - are required to be in the offset set file and only the unit, `arcsec`, is allowed. The file names should +`filenames`, `raoffset` and `decoffset`. The units of the ra and dec offsets +are required to be in the offset file and only the unit, `arcsec`, is allowed. The file names should not contain the directory path. The offset asdf filename can be any name, but it must have the `asdf` extension. - Below `num` is the number of files. From 7cba5e4fc4ac84cbdd9fc116e58f7881449e4c6d Mon Sep 17 00:00:00 2001 From: jemorrison Date: Fri, 13 Sep 2024 08:56:26 -0700 Subject: [PATCH 22/37] fixed nirspec offset issue --- jwst/cube_build/ifu_cube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 9545cf4451..56e5b72f4c 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1560,7 +1560,7 @@ def map_detector_to_outputframe(self, this_par1, (x, y, ra, dec, wave_all, slice_no_all, dwave_all, corner_coord_all) = sky_result elif self.instrument == 'NIRSPEC': - sky_result = self.map_nirspec_pixel_to_sky(input_model) + sky_result = self.map_nirspec_pixel_to_sky(input_model, offsets) (x, y, ra, dec, wave_all, slice_no_all, dwave_all, corner_coord_all) = sky_result # ______________________________________________________________________________ From add9f8b31f0942ead094b217962d4ca291bdefcd Mon Sep 17 00:00:00 2001 From: jemorrison Date: Fri, 13 Sep 2024 09:12:39 -0700 Subject: [PATCH 23/37] CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4e8212be57..bc92289e1e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -83,7 +83,7 @@ cube_build - Replaced deep copies of NIRSpec WCS objects within most loops. [#8793] - Allow the user to provide ra and dec shifts to apply for each file to fine - tune the WCS. [#JP-3364] + tune the WCS. [#8720] datamodels ---------- From 30370f6d5a8ca18cec74206ca7b239d0f11f6d31 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Fri, 13 Sep 2024 20:18:51 -0700 Subject: [PATCH 24/37] update docs --- docs/jwst/cube_build/arguments.rst | 40 ++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index abd08926b1..88e0e2c8dd 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -92,9 +92,8 @@ The following arguments control the size and sampling characteristics of the out The odd integer number of spaxels to use in the y dimension of the tangent plane. ``offset_file [string]`` - The string contains the name of the file holding ra and dec offsets to apply to each input file. This file - must be an asdf file and the it has a specific format. It is assumed the user has determined the ra and dec - offsets to apply to the data. For details on how to construct the format of the offset file see + The string contains the name of the file holding RA and Dec offsets to apply to each input file. This file + must be an asdf file with a specific format. For details on how to construct the offset file see :ref:`offsets`. @@ -135,13 +134,15 @@ A parameter only used for investigating which detector pixels contributed to a c Creating an offset file ----------------------- -The offset file is an ASDF formated file :``_ stands for "Advanced Scientific Data. For each -input file in the spec3 assocation used to build the IFU cubes, the offset files needs to have a corresponding right ascension and declination offset given arc seconds. +The offset file is an ASDF formatted file :``_ stands for "Advanced Scientific Data. +For each input file in the spec3 association used to build the IFU cubes, the offset file needs to have a +corresponding right ascension and declination offset given in arc seconds. + Below is an example of how to make an ASDF offset file. It is assumed the user has determined the offsets to apply to the data in each file. The offsets information is stored in three lists: -`filenames`, `raoffset` and `decoffset`. The units of the ra and dec offsets +`filenames`, `raoffset` and `decoffset`. The units of the Ra and Dec offsets are required to be in the offset file and only the unit, `arcsec`, is allowed. The file names should -not contain the directory path. The offset asdf filename can be any name, but it must have the `asdf` extension. +not contain the directory path. The offset file can have any name, but it must have the `asdf` extension. Below `num` is the number of files. @@ -165,7 +166,30 @@ Below `num` is the number of files. "decoffset": decoffset } af = asdf.AsdfFile(tree) - af.write_to(input_dir + 'offsets.asdf') + af.write_to('offsets.asdf') af.close() +Or lets say there a small number of files in the assocations. The filename, raoffset and decoffset can be set +in the code. For example, if there are three files in the assocation the offset file can be created as follows: + +.. code-block:: python + + import asdf + import astropy.units as u + + filename = ['file1.fits', 'file2.fits', 'file3.fits'] + raoffset = [0.0, -1.0, 1.0] + decoffset = [0.0, 1.0, -1.0] + + tree = { + "units": str(u.arcsec), + "filename": filename, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + af.write_to('offsets.asdf') + af.close() + + From c356158df3e11bbc480f3b4c62c0ef2e1c8802c1 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Fri, 13 Sep 2024 20:22:36 -0700 Subject: [PATCH 25/37] update docs --- docs/jwst/cube_build/arguments.rst | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 88e0e2c8dd..b66b53b419 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -143,21 +143,16 @@ offsets to apply to the data in each file. The offsets information is stored in `filenames`, `raoffset` and `decoffset`. The units of the Ra and Dec offsets are required to be in the offset file and only the unit, `arcsec`, is allowed. The file names should not contain the directory path. The offset file can have any name, but it must have the `asdf` extension. -Below `num` is the number of files. - +An example of making an offset file for an association containing three files is: .. code-block:: python import asdf import astropy.units as u - filename = [] - raoffset = [] - decoffset = [] - for i in range(num): - filename.append(file[i]) - raoffset.append(ra_center1[i]) - decoffset.append(dec_center1[i]) + filename = ['file1.fits', 'file2.fits', 'file3.fits'] + raoffset = [0.0, -1.0, 1.0] + decoffset = [0.0, 1.0, -1.0] tree = { "units": str(u.arcsec), @@ -168,19 +163,25 @@ Below `num` is the number of files. af = asdf.AsdfFile(tree) af.write_to('offsets.asdf') af.close() + + -Or lets say there a small number of files in the assocations. The filename, raoffset and decoffset can be set -in the code. For example, if there are three files in the assocation the offset file can be created as follows: +An exmaple of making an offset file for `num` files is + .. code-block:: python import asdf import astropy.units as u - filename = ['file1.fits', 'file2.fits', 'file3.fits'] - raoffset = [0.0, -1.0, 1.0] - decoffset = [0.0, 1.0, -1.0] + filename = [] + raoffset = [] + decoffset = [] + for i in range(num): + filename.append(file[i]) + raoffset.append(ra_center1[i]) + decoffset.append(dec_center1[i]) tree = { "units": str(u.arcsec), @@ -191,5 +192,5 @@ in the code. For example, if there are three files in the assocation the offset af = asdf.AsdfFile(tree) af.write_to('offsets.asdf') af.close() - + From cc66ae9e2fb3303c77e4325fd85e16f9a0d87e8d Mon Sep 17 00:00:00 2001 From: jemorrison Date: Mon, 16 Sep 2024 15:37:46 -0700 Subject: [PATCH 26/37] changed how ra offset values are determined --- docs/jwst/cube_build/arguments.rst | 48 ++-- jwst/cube_build/cube_build_step.py | 21 +- jwst/cube_build/ifu_cube.py | 158 +++++++---- jwst/cube_build/tests/test_configuration.py | 109 -------- jwst/cube_build/tests/test_offset.py | 290 ++++++++++++++++++++ 5 files changed, 450 insertions(+), 176 deletions(-) create mode 100644 jwst/cube_build/tests/test_offset.py diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index b66b53b419..74a1f72e46 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -167,30 +167,44 @@ An example of making an offset file for an association containing three files is -An exmaple of making an offset file for `num` files is +An example of making an offset file for `num` files +where the user has set up list called `file` containing the `num` filenames. +The cooresponding Ra and Dec offsets, both containing num values, are stored in lists called, +`ra_offset` and `dec_offset` .. code-block:: python import asdf import astropy.units as u - - filename = [] - raoffset = [] - decoffset = [] - for i in range(num): - filename.append(file[i]) - raoffset.append(ra_center1[i]) - decoffset.append(dec_center1[i]) - - tree = { - "units": str(u.arcsec), - "filename": filename, - "raoffset": raoffset, - "decoffset": decoffset + def create_offset_asdf(files, ra_offset, dec_offset): + + filename = [] + raoffset = [] + decoffset = [] + num = len(files) + for i in range(num): + filename.append(files[i]) + raoffset.append(ra_offset[i]) + decoffset.append(dec_offset[i]) + + tree = { + "units": str(u.arcsec), + "filename": filename, + "raoffset": raoffset, + "decoffset": decoffset } af = asdf.AsdfFile(tree) - af.write_to('offsets.asdf') - af.close() + af.write_to( 'offsets.asdf') +Set up the lists and call the above function: + +.. code-block:: python + + files = ['test1.fits', 'test2.fits', 'test3.fits'] + ra_offset = [0.1, 0.12, 0.13] + dec_offset = [0.14, 0.15, 0.16] + create_offset_asdf(files, ra_offset, dec_offset) + + diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 08f00d2d52..3341b6ba18 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -243,6 +243,7 @@ def process(self, input): # Each row in the offset list contain a filename, ra offset and dec offset. # The offset list is an asdf file. self.offsets = None + if self.offset_file is not None: offsets = self.check_offset_file() if offsets is not None: @@ -578,7 +579,8 @@ def check_offset_file(self): self.log.error('Turning off adjusting by offsets ') af.close() return None - + + # check that all the file names in input_model are in the offset filename for model in self.input_models: file_check = model.meta.filename if file_check in offset_filename: @@ -588,10 +590,25 @@ def check_offset_file(self): self.log.error('Turning off adjusting by offsets') af.close() return None + # check that all the lists have the same length + len_file = len(offset_filename) + len_ra = len(offset_ra) + len_dec = len(offset_dec) + if (len_file != len_ra or len_ra != len_dec or len_file != len_dec): + self.log.error('The offset file does not have the same number of values for filename, offset_ra, offset_dec') + self.log.error('Turning off adjusting by offsets') + af.close() + return None + + # The offset file has passed tests so set the offset dictionary offsets = {} offsets['filename'] = offset_filename offsets['raoffset'] = offset_ra offsets['decoffset'] = offset_dec - + n = len(offsets['raoffset']) + # convert to degrees + for i in range(n): + offsets['raoffset'][i] = offsets['raoffset'][i]/3600.0 + offsets['decoffset'][i] = offsets['decoffset'][i]/3600.0 af.close() return offsets diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 56e5b72f4c..e325424407 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -14,6 +14,8 @@ from stdatamodels.jwst import datamodels from stdatamodels.jwst.datamodels import dqflags from stdatamodels.jwst.transforms.models import _toindex +from astropy import units +from astropy.coordinates import SkyCoord from ..model_blender import blendmeta from ..assign_wcs import pointing @@ -602,7 +604,8 @@ def build_ifucube(self): # ________________________________________________________________________________ # loop over the files that cover the spectral range the cube is for - input_model = datamodels.open(input) + #input_model = datamodels.open(input) + input_model = input self.input_models_this_cube.append(input_model.copy()) # set up input_model to be first file used to copy in basic header info # to ifucube meta data @@ -1235,7 +1238,6 @@ def setup_ifucube_wcs(self): # _____________________________________________________________________________ self.cdelt1 = self.spatial_size self.cdelt2 = self.spatial_size - deg2rad = math.pi / 180.0 if self.linear_wavelength: self.cdelt3 = self.spectral_size @@ -1315,7 +1317,6 @@ def setup_ifucube_wcs(self): # find the median center declination if we have an offset file if self.offsets is not None: - decs = [] for k in range(n): input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] input_model = datamodels.open(input_file) @@ -1325,17 +1326,7 @@ def setup_ifucube_wcs(self): cb2 = float(s[6]) cb3 = float(s[8]) cb4 = float(s[10]) - m = (cb1 + cb2 + cb3 + cb4)/4 - decs.append(m) - - self.median_dec = np.nanmedian(decs) - # fold in the median_dec information into ra offset - noffsets = len(self.offsets['raoffset']) - # convert ra and dec offsets to degrees and adjust ra offset for cos(dec) - for im in range(noffsets): - self.offsets['raoffset'][im] = (self.offsets['raoffset'][im]/3600.0)/np.cos(self.median_dec*deg2rad) - self.offsets['decoffset'][im] = self.offsets['decoffset'][im]/3600.0 - + for k in range(n): lmin = 0.0 lmax = 0.0 @@ -1408,15 +1399,38 @@ def setup_ifucube_wcs(self): ca1, cb1, ca2, cb2, ca3, cb3, ca4, cb4, lmin, lmax = ch_corners # now append this model spatial and spectral corner - corner_a.append(ca1 + raoffset) - corner_a.append(ca2 + raoffset) - corner_a.append(ca3 + raoffset) - corner_a.append(ca4 + raoffset) - - corner_b.append(cb1 + decoffset) - corner_b.append(cb2 + decoffset) - corner_b.append(cb3 + decoffset) - corner_b.append(cb4 + decoffset) + if self.offsets is not None: + c1 = SkyCoord(ca1, cb1, unit='deg') + c2 = SkyCoord(ca2, cb2, unit='deg') + c3 = SkyCoord(ca3, cb3, unit='deg') + c4 = SkyCoord(ca4, cb4, unit='deg') + raoffset = raoffset* units.deg + decoffset = decoffset* units.deg + + c1_new = c1.spherical_offsets_by(raoffset, decoffset) + c2_new = c2.spherical_offsets_by(raoffset, decoffset) + c3_new = c3.spherical_offsets_by(raoffset, decoffset) + c4_new = c4.spherical_offsets_by(raoffset, decoffset) + ca1 = c1_new.ra.value + cb1 = c1_new.dec.value + + ca2 = c2_new.ra.value + cb2 = c2_new.dec.value + + ca3 = c3_new.ra.value + cb3 = c3_new.dec.value + + ca4 = c4_new.ra.value + cb4 = c4_new.dec.value + corner_a.append(ca1) + corner_a.append(ca2) + corner_a.append(ca3) + corner_a.append(ca4) + + corner_b.append(cb1) + corner_b.append(cb2) + corner_b.append(cb3) + corner_b.append(cb4) lambda_min.append(lmin) lambda_max.append(lmax) @@ -1760,7 +1774,6 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, slice_no = None # Slice number dwave = None corner_coord = None - deg2rad = math.pi / 180.0 raoffset = 0.0 decoffset = 0.0 @@ -1771,9 +1784,10 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, raoffset = offsets['raoffset'][index] decoffset = offsets['decoffset'][index] log.info("Ra and dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", - raoffset*3600.0*np.cos(self.median_dec*deg2rad), + raoffset*3600.0, decoffset*3600.0, filename) - + raoffset = raoffset* units.deg + decoffset = decoffset* units.deg # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been # subtracted - subtract it. @@ -1809,8 +1823,13 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, # if self.coord_system == 'skyalign' or self.coord_system == 'ifualign': ra, dec, wave = input_model.meta.wcs(x, y) - ra = ra + raoffset - dec = dec + decoffset + + if offsets is not None: + c1 = SkyCoord(ra, dec, unit='deg') + c1_new = c1.spherical_offsets_by(raoffset, decoffset) + ra = c1_new.ra.value + dec = c1_new.dec.value + valid1 = ~np.isnan(ra) ra = ra[valid1] dec = dec[valid1] @@ -1850,15 +1869,28 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, input_model.meta.wcs.output_frame, alpha2, beta - dbeta * pixfrac / 2., wave) - ra1 = ra1 + raoffset - ra2 = ra2 + raoffset - ra3 = ra3 + raoffset - ra4 = ra4 + raoffset + if offsets is not None: + c1 = SkyCoord(ra1, dec1, unit='deg') + c2 = SkyCoord(ra2, dec2, unit='deg') + c3 = SkyCoord(ra3, dec3, unit='deg') + c4 = SkyCoord(ra4, dec4, unit='deg') + + c1_new = c1.spherical_offsets_by(raoffset, decoffset) + c2_new = c2.spherical_offsets_by(raoffset, decoffset) + c3_new = c3.spherical_offsets_by(raoffset, decoffset) + c4_new = c4.spherical_offsets_by(raoffset, decoffset) + ra1 = c1_new.ra.value + dec1 = c1_new.dec.value + + ra2 = c2_new.ra.value + dec2 = c2_new.dec.value - dec1 = dec1 + decoffset - dec2 = dec2 + decoffset - dec3 = dec3 + decoffset - dec4 = dec4 + decoffset + ra3 = c3_new.ra.value + dec3 = c3_new.dec.value + + ra4 = c4_new.ra.value + dec4 = c4_new.dec.value + corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] @@ -1895,7 +1927,8 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): decoffset = offsets['decoffset'][index] log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", raoffset, decoffset, filename) - + raoffset = raoffset* units.deg + decoffset = decoffset* units.deg # initialize the ra,dec, and wavelength arrays # we will loop over slice_nos and fill in values # the flag_det will be set when a slice_no pixel is filled in @@ -2043,21 +2076,50 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): valid_data = np.where(flag_det == 1) y, x = valid_data - - ra = ra_det[valid_data] + raoffset - dec = dec_det[valid_data] + decoffset + wave = lam_det[valid_data] slice_no = slice_det[valid_data] dwave = dwave_det[valid_data] - ra1 = ra1_det[valid_data] + raoffset - ra2 = ra2_det[valid_data] + raoffset - ra3 = ra3_det[valid_data] + raoffset - ra4 = ra4_det[valid_data] + raoffset - dec1 = dec1_det[valid_data] + decoffset - dec2 = dec2_det[valid_data] + decoffset - dec3 = dec3_det[valid_data] + decoffset - dec4 = dec4_det[valid_data] + decoffset + + ra = ra_det[valid_data] + dec = dec_det[valid_data] + ra1 = ra1_det[valid_data] + ra2 = ra2_det[valid_data] + ra3 = ra3_det[valid_data] + ra4 = ra4_det[valid_data] + dec1 = dec1_det[valid_data] + dec2 = dec2_det[valid_data] + dec3 = dec3_det[valid_data] + dec4 = dec4_det[valid_data] + + if offsets is not None: + c1 = SkyCoord(ra, dec, unit='deg') + + c1_new = c1.spherical_offsets_by(raoffset, decoffset) + ra = c1_new.ra.value + dec = c1_new.dec.value + + c1 = SkyCoord(ra1, dec1, unit='deg') + c2 = SkyCoord(ra2, dec2, unit='deg') + c3 = SkyCoord(ra3, dec3, unit='deg') + c4 = SkyCoord(ra4, dec4, unit='deg') + c1_new = c1.spherical_offsets_by(raoffset, decoffset) + c2_new = c2.spherical_offsets_by(raoffset, decoffset) + c3_new = c3.spherical_offsets_by(raoffset, decoffset) + c4_new = c4.spherical_offsets_by(raoffset, decoffset) + ra1 = c1_new.ra.value + dec1 = c1_new.dec.value + + ra2= c2_new.ra.value + dec2 = c2_new.dec.value + + ra3 = c3_new.ra.value + dec3 = c3_new.dec.value + + ra4= c4_new.ra.value + dec4 = c4_new.dec.value + corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) return sky_result diff --git a/jwst/cube_build/tests/test_configuration.py b/jwst/cube_build/tests/test_configuration.py index 0ef5ab1e2e..9a9eee11db 100644 --- a/jwst/cube_build/tests/test_configuration.py +++ b/jwst/cube_build/tests/test_configuration.py @@ -102,50 +102,6 @@ 'ystart': 1 } - -@pytest.fixture(scope='module') -def offset_file(tmp_path_factory): - """ Generate a offset file """ - - filename = tmp_path_factory.mktemp('offset') - filename = filename / 'offset.asdf' - - testfile = ['test1.fits', 'test2.fits'] - raoffset = [0.0, 0.1] - decoffset = [0.0, 0.15] - tree = { - "units": str(u.arcsec), - "filename": testfile, - "raoffset": raoffset, - "decoffset": decoffset - } - af = asdf.AsdfFile(tree) - af.write_to(filename) - af.close() - return filename - - -@pytest.fixture(scope='module') -def offset_file_arcmin(tmp_path_factory): - """ Generate a offset file with units = arcmin """ - - filename = tmp_path_factory.mktemp('offset') - filename = filename / 'offset_arcmin.asdf' - - testfile = ['test1.fits', 'test2.fits'] - raoffset = [0.0, 0.1] - decoffset = [0.0, 0.15] - tree = { - "units": str(u.arcmin), - "filename": testfile, - "raoffset": raoffset, - "decoffset": decoffset - } - af = asdf.AsdfFile(tree) - af.write_to(filename) - return filename - - @pytest.fixture(scope='function') def miri_ifushort_short(): """ Generate a IFU image """ @@ -158,33 +114,6 @@ def miri_ifushort_short(): input_model.meta.cal_step.assign_wcs = 'COMPLETE' return input_model - -@pytest.fixture(scope='function') -def miri_ifushort_short_2files(): - """ Generate a IFU image """ - - input_model1 = datamodels.IFUImageModel() - input_model1.meta.wcsinfo._instance.update(wcsinfo) - input_model1.meta.instrument._instance.update(mirifushort_short) - input_model1.meta.observation._instance.update(observation) - input_model1.meta.subarray._instance.update(subarray) - input_model1.meta.cal_step.assign_wcs = 'COMPLETE' - input_model1.meta.filename = 'test1.fits' - - input_model2 = datamodels.IFUImageModel() - input_model2.meta.wcsinfo._instance.update(wcsinfo) - input_model2.meta.instrument._instance.update(mirifushort_short) - input_model2.meta.observation._instance.update(observation) - input_model2.meta.subarray._instance.update(subarray) - input_model2.meta.cal_step.assign_wcs = 'COMPLETE' - input_model2.meta.filename = 'test2.fits' - - input_models = [] - input_models.append(input_model1) - input_models.append(input_model2) - return input_models - - @pytest.fixture(scope='function') def miri_full_coverage(): """ Generate a IFU images SHORT, LONG for all three bands """ @@ -539,41 +468,3 @@ def test_calspec3_config_nirspec_multi(tmp_cwd, nirspec_medium_coverage): assert cube_pars['1']['par1'] == ['g140m', 'g235m'] assert cube_pars['1']['par2'] == ['f100lp', 'f170lp'] - - -def test_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): - """ Test validation of the offset configuration""" - - # first test that it is a valid asdf file and has what is needed - step = CubeBuildStep() - step.input_models = miri_ifushort_short_2files - - step.offset_file = offset_file - offsets = step.check_offset_file() - assert isinstance(offsets, dict) - -def test2_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): - """ Test validation of the offset configuration""" - - # Test changing one of the filenames so it is not in the list given - # in the offset_file - step = CubeBuildStep() - step.input_models = miri_ifushort_short_2files - - miri_ifushort_short_2files[0].meta.filename = 'test3.fits' - step.offset_file = offset_file - offsets = step.check_offset_file() - assert offsets is None - - -def test_offset_file_config2(tmp_cwd, miri_ifushort_short_2files, offset_file_arcmin): - """ Test validation of the offset configuration""" - - # test is the if the user set the units to arcmins - step = CubeBuildStep() - step.input_models = miri_ifushort_short_2files - - step.offset_file = offset_file_arcmin - offsets = step.check_offset_file() - assert offsets is None - diff --git a/jwst/cube_build/tests/test_offset.py b/jwst/cube_build/tests/test_offset.py new file mode 100644 index 0000000000..275764bba2 --- /dev/null +++ b/jwst/cube_build/tests/test_offset.py @@ -0,0 +1,290 @@ +""" +Unit test for Cube Build testing setting up configuration +""" + +import pytest +import sys +import math +import asdf +from stdatamodels.jwst import datamodels +import astropy.units as u +from gwcs import WCS +import numpy as np +from jwst.cube_build import CubeBuildStep +from jwst.cube_build import cube_build +from jwst.cube_build import ifu_cube +from jwst.cube_build import file_table +from jwst.cube_build import instrument_defaults +from jwst import assign_wcs + + +@pytest.fixture(scope='module') +def offset_file(tmp_path_factory): + """ Generate a offset file """ + + filename = tmp_path_factory.mktemp('offset') + filename = filename / 'offset.asdf' + + testfile = ['test1.fits', 'test2.fits'] + raoffset = [0.0, 0.1] + decoffset = [0.0, 0.15] + tree = { + "units": str(u.arcsec), + "filename": testfile, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + af.write_to(filename) + af.close() + return filename + + +@pytest.fixture(scope='module') +def offset_file_arcmin(tmp_path_factory): + """ Generate a offset file with units = arcmin """ + + filename = tmp_path_factory.mktemp('offset') + filename = filename / 'offset_arcmin.asdf' + + testfile = ['test1.fits', 'test2.fits'] + raoffset = [0.0, 0.1] + decoffset = [0.0, 0.15] + tree = { + "units": str(u.arcmin), + "filename": testfile, + "raoffset": raoffset, + "decoffset": decoffset + } + af = asdf.AsdfFile(tree) + af.write_to(filename) + return filename + +@pytest.fixture(scope='function') +def miri_ifushort_short_2files(): + """ Generate input model with 2 IFU images """ + + observation = { + 'date': '2019-01-01', + 'time': '17:00:00'} + + subarray = { + 'fastaxis': 1, + 'name': 'FULL', + 'slowaxis': 2, + 'xsize': 1032, + 'xstart': 1, + 'ysize': 1024, + 'ystart': 1 + } + + wcsinfo = { + 'dec_ref': 39.05036271706514, + 'ra_ref': 339.8149235604264 , + 'roll_ref': 217.25027556008598 , + 'v2_ref': -503.378, + 'v3_ref': -318.9992, + 'v3yangle': 0.0, + 'vparity': -1, + 's_region': 'POLYGON ICRS 339.813915797 39.049575409 339.816080118 39.049575409 339.816080118 39.051260090 339.813915797 39.051260090', + 'spectral_region': ([4.889451133245338, 8.781164838427532]) + } + + mirifushort_short = { + 'detector': 'MIRIFUSHORT', + 'channel': '12', + 'band': 'SHORT', + 'name': 'MIRI' + } + + input_model1 = datamodels.IFUImageModel() + input_model1.meta.exposure.type = 'MIR_MRS' + input_model1.meta.wcsinfo._instance.update(wcsinfo) + input_model1.meta.instrument._instance.update(mirifushort_short) + input_model1.meta.observation._instance.update(observation) + input_model1.meta.subarray._instance.update(subarray) + input_model1.meta.cal_step.assign_wcs = 'COMPLETE' + input_model1.meta.filename = 'test1.fits' + input_model1.data = np.random.random((1024, 1032)) + + input_model2 = datamodels.IFUImageModel() + input_model2.meta.exposure.type = 'MIR_MRS' + input_model2.meta.wcsinfo._instance.update(wcsinfo) + input_model2.meta.instrument._instance.update(mirifushort_short) + input_model2.meta.observation._instance.update(observation) + input_model2.meta.subarray._instance.update(subarray) + input_model2.meta.cal_step.assign_wcs = 'COMPLETE' + input_model2.meta.filename = 'test2.fits' + input_model2.data = np.random.random((1024, 1032)) + + input_models = [] + + step = assign_wcs.assign_wcs_step.AssignWcsStep() + refs = {} + for reftype in assign_wcs.assign_wcs_step.AssignWcsStep.reference_file_types: + refs[reftype] = step.get_reference_file(input_model1, reftype) + pipe = assign_wcs.miri.create_pipeline(input_model1, refs) + input_model1.meta.wcs = WCS(pipe) + + for reftype in assign_wcs.assign_wcs_step.AssignWcsStep.reference_file_types: + refs[reftype] = step.get_reference_file(input_model2, reftype) + pipe = assign_wcs.miri.create_pipeline(input_model2, refs) + input_model2.meta.wcs = WCS(pipe) + + input_models.append(input_model1) + input_models.append(input_model2) + return input_models + + +def test_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): + """ Test validation of the offset configuration""" + + # first test that it is a valid asdf file and has what is needed + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + step.offset_file = offset_file + offsets = step.check_offset_file() + assert isinstance(offsets, dict) + + +def test2_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): + """ Test validation of the offset configuration""" + + # Test changing one of the filenames so it is not in the list given + # in the offset_file + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + miri_ifushort_short_2files[0].meta.filename = 'test3.fits' + step.offset_file = offset_file + offsets = step.check_offset_file() + assert offsets is None + + +def test_offset_file_units(tmp_cwd, miri_ifushort_short_2files, offset_file_arcmin): + """ Test offsets are not used when units are arc minutes""" + + # test is the if the user set the units to arcmins + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + + step.offset_file = offset_file_arcmin + offsets = step.check_offset_file() + assert offsets is None + + +def test_read_offset_file(miri_ifushort_short_2files, offset_file): + """ Test offset file has been read in correctly""" + + step = CubeBuildStep() + step.input_models = miri_ifushort_short_2files + step.offset_file = offset_file + offsets = step.check_offset_file() + # Test that the offset file is read in and is a dictionary + assert isinstance(offsets, dict) + + pars_input = {} + pars_input['channel'] = [] + pars_input['subchannel'] = [] + pars_input['filter'] = [] + pars_input['grating'] = [] + weighting = 'drizzle' + output_type = 'multi' + single = False + par_filename = 'None' + + # set up pars needed for CubeData class + pars = { + 'channel': pars_input['channel'], + 'subchannel': pars_input['subchannel'], + 'grating': pars_input['grating'], + 'filter': pars_input['filter'], + 'weighting': weighting, + 'single': single, + 'output_type': output_type, + 'offset_file': offset_file} + + cubeinfo = cube_build.CubeData( + miri_ifushort_short_2files, + par_filename, + **pars) + + master_table = file_table.FileTable() + this_instrument = master_table.set_file_table( + cubeinfo.input_models) + + cubeinfo.instrument = this_instrument + cubeinfo.determine_band_coverage(master_table) + num_cubes, cube_pars = cubeinfo.number_cubes() + # test with output_type = mulit we get 1 cube + # test that cube info sets up the correct channels and band for data + assert num_cubes == 1 + assert cube_pars['1']['par1'] == ['1','2'] + assert cube_pars['1']['par2'] == ['short','short'] + + wave_min = 4.88 + wave_max = 8.78 + + # set up par for IFUCubeData CLASS + pars_cube = { + 'scalexy': 0.0, + 'scalew': 0.0, + 'interpolation': 'drizzle', + 'weighting': 'drizzle', + 'weight_power':None, + 'coord_system': 'skyalign', + 'ra_center': None, + 'dec_center': None, + 'cube_pa': None, + 'nspax_x': None, + 'nspax_y': None, + 'rois': None, + 'riow': None, + 'wavemin': wave_min, + 'wavemax': wave_max, + 'skip_dqflagging': False, + 'offsets': offsets, + 'debug_spaxel': '0 0 0'} + + pipeline = 3 + list_par1 = ['1','2'] + list_par2 = ['short','short'] + output_name_base = 'TEMP' + + instrument_info = instrument_defaults.InstrumentInfo() + + thiscube = ifu_cube.IFUCubeData( + pipeline, + miri_ifushort_short_2files, + output_name_base, + output_type, + this_instrument, + list_par1, + list_par2, + instrument_info, + master_table, + **pars_cube) + + thiscube.linear_wavelength = True + thiscube.spatial_size = 0.13 + thiscube.spectral_size = 0.001 + thiscube.setup_ifucube_wcs() + + # test the offset file was read in correctly + filename = ['test1.fits', 'test2.fits'] + raoffset = [0.0, 0.1] + decoffset = [0.0, 0.15] + + ravalues = thiscube.offsets['raoffset'] + decvalues = thiscube.offsets['decoffset'] + + assert thiscube.offsets['filename'] == filename + + assert math.isclose(ravalues[0]*3600.0, raoffset[0], abs_tol=0.0001) + assert math.isclose(ravalues[1]*3600.0, raoffset[1], abs_tol=0.0001) + assert math.isclose(decvalues[0]*3600.0, decoffset[0], abs_tol=0.0001) + assert math.isclose(decvalues[1]*3600.0, decoffset[1], abs_tol=0.0001) + + + From 9c0aaca6503351453d03e7ba0e69f3ff080aa0d9 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 17 Sep 2024 14:03:22 -0700 Subject: [PATCH 27/37] fix doc error --- docs/jwst/cube_build/arguments.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 74a1f72e46..ca749d5d21 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -145,6 +145,7 @@ are required to be in the offset file and only the unit, `arcsec`, is allowed. T not contain the directory path. The offset file can have any name, but it must have the `asdf` extension. An example of making an offset file for an association containing three files is: + .. code-block:: python import asdf @@ -167,10 +168,10 @@ An example of making an offset file for an association containing three files is -An example of making an offset file for `num` files -where the user has set up list called `file` containing the `num` filenames. -The cooresponding Ra and Dec offsets, both containing num values, are stored in lists called, -`ra_offset` and `dec_offset` +Below is an example of making an offset file for `num` files. +The user has set up the `file` list containing the `num` filenames and the +cooresponding Ra and Dec offsets lists, both containing num values. In this example +these list are called `ra_offset` and `dec_offset` .. code-block:: python @@ -202,9 +203,9 @@ Set up the lists and call the above function: .. code-block:: python - files = ['test1.fits', 'test2.fits', 'test3.fits'] - ra_offset = [0.1, 0.12, 0.13] - dec_offset = [0.14, 0.15, 0.16] + files = ['test1.fits', 'test2.fits', 'test3.fits', 'test4,fits', 'test5.fits'] + ra_offset = [0.1, 0.12, 0.13, 0.11, 0.12] + dec_offset = [0.14, 0.15, 0.16, 0.01, 0.1] create_offset_asdf(files, ra_offset, dec_offset) From d5c6c7d86e150e21935747cbdbfd1dd94f864569 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 17 Sep 2024 15:10:22 -0700 Subject: [PATCH 28/37] update comments and doc --- docs/jwst/cube_build/arguments.rst | 1 - jwst/cube_build/ifu_cube.py | 29 +++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index ca749d5d21..90b3901bc8 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -167,7 +167,6 @@ An example of making an offset file for an association containing three files is - Below is an example of making an offset file for `num` files. The user has set up the `file` list containing the `num` filenames and the cooresponding Ra and Dec offsets lists, both containing num values. In this example diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index e325424407..54270a7481 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1316,16 +1316,16 @@ def setup_ifucube_wcs(self): log.debug('number of files %d', n) # find the median center declination if we have an offset file - if self.offsets is not None: - for k in range(n): - input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] - input_model = datamodels.open(input_file) - spatial_box = input_model.meta.wcsinfo.s_region - s = spatial_box.split(' ') - cb1 = float(s[4]) - cb2 = float(s[6]) - cb3 = float(s[8]) - cb4 = float(s[10]) + #if self.offsets is not None: + # for k in range(n): + # input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] + # input_model = datamodels.open(input_file) + # spatial_box = input_model.meta.wcsinfo.s_region + # s = spatial_box.split(' ') + # cb1 = float(s[4]) + # cb2 = float(s[6]) + # cb3 = float(s[8]) + # cb4 = float(s[10]) for k in range(n): lmin = 0.0 @@ -1824,6 +1824,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, # if self.coord_system == 'skyalign' or self.coord_system == 'ifualign': ra, dec, wave = input_model.meta.wcs(x, y) + # offset the central pixel if offsets is not None: c1 = SkyCoord(ra, dec, unit='deg') c1_new = c1.spherical_offsets_by(raoffset, decoffset) @@ -1869,6 +1870,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, input_model.meta.wcs.output_frame, alpha2, beta - dbeta * pixfrac / 2., wave) + # now offset the pixel corners if offsets is not None: c1 = SkyCoord(ra1, dec1, unit='deg') c2 = SkyCoord(ra2, dec2, unit='deg') @@ -1891,7 +1893,6 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, ra4 = c4_new.ra.value dec4 = c4_new.dec.value - corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) @@ -2081,7 +2082,6 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): slice_no = slice_det[valid_data] dwave = dwave_det[valid_data] - ra = ra_det[valid_data] dec = dec_det[valid_data] ra1 = ra1_det[valid_data] @@ -2093,13 +2093,14 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): dec3 = dec3_det[valid_data] dec4 = dec4_det[valid_data] - if offsets is not None: + if offsets is not None: + # central pixel c1 = SkyCoord(ra, dec, unit='deg') - c1_new = c1.spherical_offsets_by(raoffset, decoffset) ra = c1_new.ra.value dec = c1_new.dec.value + # pixel corners c1 = SkyCoord(ra1, dec1, unit='deg') c2 = SkyCoord(ra2, dec2, unit='deg') c3 = SkyCoord(ra3, dec3, unit='deg') From 78f702c3fde3143df7b9c87beff7c4755e015d61 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 17 Sep 2024 15:32:00 -0700 Subject: [PATCH 29/37] update docs and update log message --- docs/jwst/cube_build/arguments.rst | 8 ++++---- jwst/cube_build/ifu_cube.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 90b3901bc8..479a0253f6 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -168,10 +168,10 @@ An example of making an offset file for an association containing three files is Below is an example of making an offset file for `num` files. -The user has set up the `file` list containing the `num` filenames and the -cooresponding Ra and Dec offsets lists, both containing num values. In this example -these list are called `ra_offset` and `dec_offset` - +The user has set up three lists `file`, `ra_offset` and `dec_offset`. The `file` list +contains the filenames and the `ra_offset` and `dec_offset` contain the Ra and Dec offsets respectively. +In this example, all the list have five values. The units of the of Ra and Dec offsets are given in +the `units` value. .. code-block:: python diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 54270a7481..d2e73e44cf 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1783,7 +1783,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, index = offsets['filename'].index(filename) raoffset = offsets['raoffset'][index] decoffset = offsets['decoffset'][index] - log.info("Ra and dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", + log.info("Ra and Dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", raoffset*3600.0, decoffset*3600.0, filename) raoffset = raoffset* units.deg @@ -1926,8 +1926,9 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): index = offsets['filename'].index(filename) raoffset = offsets['raoffset'][index] decoffset = offsets['decoffset'][index] - log.info("Ra and dec offset (arc seconds) applied to file :%5.2f, %5.2f, %s", - raoffset, decoffset, filename) + log.info("Ra and Dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", + raoffset*3600.0, decoffset*3600.0, filename) + raoffset = raoffset* units.deg decoffset = decoffset* units.deg # initialize the ra,dec, and wavelength arrays From 32b4044a4968ebe59521ee3c8e825c16e6f15d89 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Tue, 17 Sep 2024 15:48:42 -0700 Subject: [PATCH 30/37] docs update --- docs/jwst/cube_build/arguments.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 479a0253f6..717095d38d 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -140,7 +140,7 @@ corresponding right ascension and declination offset given in arc seconds. Below is an example of how to make an ASDF offset file. It is assumed the user has determined the offsets to apply to the data in each file. The offsets information is stored in three lists: -`filenames`, `raoffset` and `decoffset`. The units of the Ra and Dec offsets +`filename`, `raoffset` and `decoffset`. The units of the Ra and Dec offsets are required to be in the offset file and only the unit, `arcsec`, is allowed. The file names should not contain the directory path. The offset file can have any name, but it must have the `asdf` extension. @@ -171,7 +171,7 @@ Below is an example of making an offset file for `num` files. The user has set up three lists `file`, `ra_offset` and `dec_offset`. The `file` list contains the filenames and the `ra_offset` and `dec_offset` contain the Ra and Dec offsets respectively. In this example, all the list have five values. The units of the of Ra and Dec offsets are given in -the `units` value. +the `units` value and this value must be arc seconds. .. code-block:: python From 4e665cb885c5e28ce47a371fb888dae030981823 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Wed, 18 Sep 2024 07:38:50 -0700 Subject: [PATCH 31/37] removed openning and closing model --- jwst/cube_build/ifu_cube.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index d2e73e44cf..5d3802d1ae 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -600,12 +600,10 @@ def build_ifucube(self): for ib in range(number_bands): this_par1 = self.list_par1[ib] this_par2 = self.list_par2[ib] - for input in self.master_table.FileMap[self.instrument][this_par1][this_par2]: + for input_model in self.master_table.FileMap[self.instrument][this_par1][this_par2]: # ________________________________________________________________________________ # loop over the files that cover the spectral range the cube is for - #input_model = datamodels.open(input) - input_model = input self.input_models_this_cube.append(input_model.copy()) # set up input_model to be first file used to copy in basic header info # to ifucube meta data @@ -777,8 +775,6 @@ def build_ifucube(self): result = None del spaxel_flux, spaxel_weight, spaxel_var, spaxel_iflux, result k = k + 1 - input_model.close() - del input_model # _______________________________________________________________________ # done looping over files @@ -1315,24 +1311,11 @@ def setup_ifucube_wcs(self): n = len(self.master_table.FileMap[self.instrument][this_a][this_b]) log.debug('number of files %d', n) - # find the median center declination if we have an offset file - #if self.offsets is not None: - # for k in range(n): - # input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] - # input_model = datamodels.open(input_file) - # spatial_box = input_model.meta.wcsinfo.s_region - # s = spatial_box.split(' ') - # cb1 = float(s[4]) - # cb2 = float(s[6]) - # cb3 = float(s[8]) - # cb4 = float(s[10]) - for k in range(n): lmin = 0.0 lmax = 0.0 + input_model = self.master_table.FileMap[self.instrument][this_a][this_b][k] - input_file = self.master_table.FileMap[self.instrument][this_a][this_b][k] - input_model = datamodels.open(input_file) # ________________________________________________________________________________ # If offsets are provided. Pull in ra and dec offsets. raoffset = 0.0 @@ -1434,7 +1417,6 @@ def setup_ifucube_wcs(self): lambda_min.append(lmin) lambda_max.append(lmax) - input_model.close() # ________________________________________________________________________________ # done looping over files determine final size of cube corner_a = np.array(corner_a) From 299b68caa1a180a9a99efeaf25d3719d878cabc4 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Wed, 18 Sep 2024 16:14:00 -0700 Subject: [PATCH 32/37] various updates from review --- docs/jwst/cube_build/arguments.rst | 41 -------- jwst/cube_build/cube_build_step.py | 34 +++---- jwst/cube_build/ifu_cube.py | 138 +++++++++----------------- jwst/cube_build/ifuoffset.schema.yaml | 5 +- jwst/cube_build/tests/test_offset.py | 20 ++-- 5 files changed, 69 insertions(+), 169 deletions(-) diff --git a/docs/jwst/cube_build/arguments.rst b/docs/jwst/cube_build/arguments.rst index 717095d38d..8d2bfa3053 100644 --- a/docs/jwst/cube_build/arguments.rst +++ b/docs/jwst/cube_build/arguments.rst @@ -167,44 +167,3 @@ An example of making an offset file for an association containing three files is -Below is an example of making an offset file for `num` files. -The user has set up three lists `file`, `ra_offset` and `dec_offset`. The `file` list -contains the filenames and the `ra_offset` and `dec_offset` contain the Ra and Dec offsets respectively. -In this example, all the list have five values. The units of the of Ra and Dec offsets are given in -the `units` value and this value must be arc seconds. - -.. code-block:: python - - import asdf - import astropy.units as u - def create_offset_asdf(files, ra_offset, dec_offset): - - filename = [] - raoffset = [] - decoffset = [] - num = len(files) - for i in range(num): - filename.append(files[i]) - raoffset.append(ra_offset[i]) - decoffset.append(dec_offset[i]) - - tree = { - "units": str(u.arcsec), - "filename": filename, - "raoffset": raoffset, - "decoffset": decoffset - } - af = asdf.AsdfFile(tree) - af.write_to( 'offsets.asdf') - - -Set up the lists and call the above function: - -.. code-block:: python - - files = ['test1.fits', 'test2.fits', 'test3.fits', 'test4,fits', 'test5.fits'] - ra_offset = [0.1, 0.12, 0.13, 0.11, 0.12] - dec_offset = [0.14, 0.15, 0.16, 0.01, 0.1] - create_offset_asdf(files, ra_offset, dec_offset) - - diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 3341b6ba18..30da89f019 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -11,7 +11,7 @@ from ..assign_wcs.util import update_s_region_keyword from ..stpipe import Step, record_step_status from pathlib import Path - +from astropy import units __all__ = ["CubeBuildStep"] @@ -564,51 +564,41 @@ def check_offset_file(self): try: af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') except: - self.log.error('Validation Error for offset file') - self.log.error('Turning off adjusting by offsets') - return None + schema_message = ('Validation Error for offset file. Fix the offset file. \n' + \ + 'The offset file needs to have the same number of elements the filename, raoffset and decoffset lists.\n' +\ + 'The units need to provided and only arcsec is allowed.') + raise Exception(schema_message) offset_filename = af['filename'] offset_ra = af['raoffset'] offset_dec = af['decoffset'] - offset_unit = af['units'] - if offset_unit != 'arcsec': - self.log.error('Provide the offset units in units of arcsec ') - self.log.error('Turning off adjusting by offsets ') - af.close() - return None - # check that all the file names in input_model are in the offset filename for model in self.input_models: file_check = model.meta.filename if file_check in offset_filename: continue else: - self.log.error('File in assocation is not found in offset list list %s', file_check) - self.log.error('Turning off adjusting by offsets') af.close() - return None + raise Exception('Error in offset file. A file in assocation is not found in offset list list %s', file_check) + # check that all the lists have the same length len_file = len(offset_filename) len_ra = len(offset_ra) len_dec = len(offset_dec) if (len_file != len_ra or len_ra != len_dec or len_file != len_dec): - self.log.error('The offset file does not have the same number of values for filename, offset_ra, offset_dec') - self.log.error('Turning off adjusting by offsets') af.close() - return None + raise Exception('Offset file error. The offset file does not have the same number of values for filename, offset_ra, offset_dec') + + offset_ra = offset_ra* units.arcsec + offset_dec = offset_dec* units.arcsec # The offset file has passed tests so set the offset dictionary offsets = {} offsets['filename'] = offset_filename offsets['raoffset'] = offset_ra offsets['decoffset'] = offset_dec - n = len(offsets['raoffset']) - # convert to degrees - for i in range(n): - offsets['raoffset'][i] = offsets['raoffset'][i]/3600.0 - offsets['decoffset'][i] = offsets['decoffset'][i]/3600.0 + af.close() return offsets diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index 5d3802d1ae..c94b41fa69 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -8,14 +8,13 @@ import math from astropy.stats import circmean -from astropy import units as u from gwcs import wcstools from stdatamodels.jwst import datamodels from stdatamodels.jwst.datamodels import dqflags from stdatamodels.jwst.transforms.models import _toindex -from astropy import units from astropy.coordinates import SkyCoord +from astropy import units as u from ..model_blender import blendmeta from ..assign_wcs import pointing @@ -116,7 +115,6 @@ def __init__(self, self.naxis3 = None self.cdelt3_normal = None self.rot_angle = None # rotation angle between Ra-Dec and IFU local instrument plane - self.median_dec = None self.a_min = 0 self.a_max = 0 @@ -1322,10 +1320,7 @@ def setup_ifucube_wcs(self): decoffset = 0.0 # pull out ra dec offset if it exists if self.offsets is not None: - filename = input_model.meta.filename - index = self.offsets['filename'].index(filename) - raoffset = self.offsets['raoffset'][index] - decoffset = self.offsets['decoffset'][index] + raoffset, decoffset = self.find_ra_dec_offset(input_model.meta.filename) # ________________________________________________________________________________ # Find the footprint of the image spectral_found = hasattr(input_model.meta.wcsinfo, 'spectral_region') @@ -1383,28 +1378,11 @@ def setup_ifucube_wcs(self): # now append this model spatial and spectral corner if self.offsets is not None: - c1 = SkyCoord(ca1, cb1, unit='deg') - c2 = SkyCoord(ca2, cb2, unit='deg') - c3 = SkyCoord(ca3, cb3, unit='deg') - c4 = SkyCoord(ca4, cb4, unit='deg') - raoffset = raoffset* units.deg - decoffset = decoffset* units.deg - - c1_new = c1.spherical_offsets_by(raoffset, decoffset) - c2_new = c2.spherical_offsets_by(raoffset, decoffset) - c3_new = c3.spherical_offsets_by(raoffset, decoffset) - c4_new = c4.spherical_offsets_by(raoffset, decoffset) - ca1 = c1_new.ra.value - cb1 = c1_new.dec.value - - ca2 = c2_new.ra.value - cb2 = c2_new.dec.value - - ca3 = c3_new.ra.value - cb3 = c3_new.dec.value - - ca4 = c4_new.ra.value - cb4 = c4_new.dec.value + ca1, cb1 = self.offset_coord(ca1, cb1, raoffset, decoffset) + ca2, cb2 = self.offset_coord(ca2, cb2, raoffset, decoffset) + ca3, cb3 = self.offset_coord(ca3, cb3, raoffset, decoffset) + ca4, cb4 = self.offset_coord(ca4, cb4, raoffset, decoffset) + corner_a.append(ca1) corner_a.append(ca2) corner_a.append(ca3) @@ -1761,15 +1739,11 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, decoffset = 0.0 # pull out ra dec offset if it exists if offsets is not None: - filename = input_model.meta.filename - index = offsets['filename'].index(filename) - raoffset = offsets['raoffset'][index] - decoffset = offsets['decoffset'][index] + raoffset, decoffset = self.find_ra_dec_offset(input_model.meta.filename) log.info("Ra and Dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", - raoffset*3600.0, - decoffset*3600.0, filename) - raoffset = raoffset* units.deg - decoffset = decoffset* units.deg + raoffset.value, + decoffset.value, input_model.meta.filename) + # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been # subtracted - subtract it. @@ -1808,10 +1782,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, # offset the central pixel if offsets is not None: - c1 = SkyCoord(ra, dec, unit='deg') - c1_new = c1.spherical_offsets_by(raoffset, decoffset) - ra = c1_new.ra.value - dec = c1_new.dec.value + ra, dec = self.offset_coord(ra, dec, raoffset, decoffset) valid1 = ~np.isnan(ra) ra = ra[valid1] @@ -1854,27 +1825,11 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, # now offset the pixel corners if offsets is not None: - c1 = SkyCoord(ra1, dec1, unit='deg') - c2 = SkyCoord(ra2, dec2, unit='deg') - c3 = SkyCoord(ra3, dec3, unit='deg') - c4 = SkyCoord(ra4, dec4, unit='deg') - - c1_new = c1.spherical_offsets_by(raoffset, decoffset) - c2_new = c2.spherical_offsets_by(raoffset, decoffset) - c3_new = c3.spherical_offsets_by(raoffset, decoffset) - c4_new = c4.spherical_offsets_by(raoffset, decoffset) - ra1 = c1_new.ra.value - dec1 = c1_new.dec.value - - ra2 = c2_new.ra.value - dec2 = c2_new.dec.value - - ra3 = c3_new.ra.value - dec3 = c3_new.dec.value - - ra4 = c4_new.ra.value - dec4 = c4_new.dec.value - + ra1, dec1 = self.offset_coord(ra1, dec1, raoffset, decoffset) + ra2, dec2 = self.offset_coord(ra2, dec2, raoffset, decoffset) + ra3, dec3 = self.offset_coord(ra3, dec3, raoffset, decoffset) + ra4, dec4 = self.offset_coord(ra4, dec4, raoffset, decoffset) + corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) @@ -1904,15 +1859,10 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): decoffset = 0.0 # pull out ra dec offset if it exists if offsets is not None: - filename = input_model.meta.filename - index = offsets['filename'].index(filename) - raoffset = offsets['raoffset'][index] - decoffset = offsets['decoffset'][index] + raoffset, decoffset = self.find_ra_dec_offset(input_model.meta.filename) log.info("Ra and Dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", - raoffset*3600.0, decoffset*3600.0, filename) + raoffset.value, decoffset.value, input_model.meta.filename) - raoffset = raoffset* units.deg - decoffset = decoffset* units.deg # initialize the ra,dec, and wavelength arrays # we will loop over slice_nos and fill in values # the flag_det will be set when a slice_no pixel is filled in @@ -2078,32 +2028,14 @@ def map_nirspec_pixel_to_sky(self, input_model, offsets): if offsets is not None: # central pixel - c1 = SkyCoord(ra, dec, unit='deg') - c1_new = c1.spherical_offsets_by(raoffset, decoffset) - ra = c1_new.ra.value - dec = c1_new.dec.value + ra, dec = self.offset_coord(ra, dec, raoffset, decoffset) # pixel corners - c1 = SkyCoord(ra1, dec1, unit='deg') - c2 = SkyCoord(ra2, dec2, unit='deg') - c3 = SkyCoord(ra3, dec3, unit='deg') - c4 = SkyCoord(ra4, dec4, unit='deg') - c1_new = c1.spherical_offsets_by(raoffset, decoffset) - c2_new = c2.spherical_offsets_by(raoffset, decoffset) - c3_new = c3.spherical_offsets_by(raoffset, decoffset) - c4_new = c4.spherical_offsets_by(raoffset, decoffset) - ra1 = c1_new.ra.value - dec1 = c1_new.dec.value - - ra2= c2_new.ra.value - dec2 = c2_new.dec.value - - ra3 = c3_new.ra.value - dec3 = c3_new.dec.value - - ra4= c4_new.ra.value - dec4 = c4_new.dec.value - + ra1, dec1 = self.offset_coord(ra1, dec1, raoffset, decoffset) + ra2, dec2 = self.offset_coord(ra2, dec2, raoffset, decoffset) + ra3, dec3 = self.offset_coord(ra3, dec3, raoffset, decoffset) + ra4, dec4 = self.offset_coord(ra4, dec4, raoffset, decoffset) + corner_coord = [ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4] sky_result = (x, y, ra, dec, wave, slice_no, dwave, corner_coord) return sky_result @@ -2504,7 +2436,27 @@ def blend_output_metadata(self, IFUCube): ], ) + # ******************************************************************************** + def find_ra_dec_offset(self, filename): + """ Match the filename in the offset list with input_model.meta.filename and return + the corresponding Ra and Dec offset + """ + + index = self.offsets['filename'].index(filename) + raoffset = self.offsets['raoffset'][index] + decoffset = self.offsets['decoffset'][index] + return raoffset, decoffset + + # ******************************************************************************** + def offset_coord(self, ra, dec, raoffset, decoffset): + coord = SkyCoord(ra, dec, unit='deg') + coord_new = coord.spherical_offsets_by(raoffset, decoffset) + + ra_new = coord_new.ra.value + dec_new = coord_new.dec.value + return ra_new, dec_new + class IncorrectInput(Exception): """ Raises an exception if input parameter, Interpolation, is set to area when more than one file is used to build the cube. diff --git a/jwst/cube_build/ifuoffset.schema.yaml b/jwst/cube_build/ifuoffset.schema.yaml index c2c7d40c6a..cd9d2737a4 100644 --- a/jwst/cube_build/ifuoffset.schema.yaml +++ b/jwst/cube_build/ifuoffset.schema.yaml @@ -7,9 +7,8 @@ type: object properties: units: description: Units of the ra and dec offset values. - anyOf: - - type: string - - $ref: http://stsci.edu/schemas/asdf/unit/unit-1.0.0 + type: string + enum: ['arcsec'] filename: description: list of filenames type: array diff --git a/jwst/cube_build/tests/test_offset.py b/jwst/cube_build/tests/test_offset.py index 275764bba2..58149a5bd5 100644 --- a/jwst/cube_build/tests/test_offset.py +++ b/jwst/cube_build/tests/test_offset.py @@ -158,8 +158,9 @@ def test2_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): miri_ifushort_short_2files[0].meta.filename = 'test3.fits' step.offset_file = offset_file - offsets = step.check_offset_file() - assert offsets is None + + with pytest.raises(Exception): + offsets = step.check_offset_file() def test_offset_file_units(tmp_cwd, miri_ifushort_short_2files, offset_file_arcmin): @@ -170,9 +171,8 @@ def test_offset_file_units(tmp_cwd, miri_ifushort_short_2files, offset_file_arcm step.input_models = miri_ifushort_short_2files step.offset_file = offset_file_arcmin - offsets = step.check_offset_file() - assert offsets is None - + with pytest.raises(Exception): + offsets = step.check_offset_file() def test_read_offset_file(miri_ifushort_short_2files, offset_file): """ Test offset file has been read in correctly""" @@ -276,15 +276,15 @@ def test_read_offset_file(miri_ifushort_short_2files, offset_file): raoffset = [0.0, 0.1] decoffset = [0.0, 0.15] - ravalues = thiscube.offsets['raoffset'] + ravalues = thiscube.offsets['raoffset'] decvalues = thiscube.offsets['decoffset'] assert thiscube.offsets['filename'] == filename - assert math.isclose(ravalues[0]*3600.0, raoffset[0], abs_tol=0.0001) - assert math.isclose(ravalues[1]*3600.0, raoffset[1], abs_tol=0.0001) - assert math.isclose(decvalues[0]*3600.0, decoffset[0], abs_tol=0.0001) - assert math.isclose(decvalues[1]*3600.0, decoffset[1], abs_tol=0.0001) + assert math.isclose(ravalues[0].value, raoffset[0], abs_tol=0.0001) + assert math.isclose(ravalues[1].value, raoffset[1], abs_tol=0.0001) + assert math.isclose(decvalues[0].value, decoffset[0], abs_tol=0.0001) + assert math.isclose(decvalues[1].value, decoffset[1], abs_tol=0.0001) From 3c51fca28cd7b24983c2e71ead92a7d66c489916 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 19 Sep 2024 09:39:21 -0700 Subject: [PATCH 33/37] update comments --- jwst/cube_build/cube_build_step.py | 17 ++++++++++------- jwst/cube_build/ifu_cube.py | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 30da89f019..33a349eb8c 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -64,7 +64,7 @@ class CubeBuildStep (Step): search_output_file = boolean(default=false) output_use_model = boolean(default=true) # Use filenames in the output models suffix = string(default='s3d') - offset_file = string(default=None) + offset_file = string(default=None) # Filename containing a list of Ra and Dec offsets to apply to files. debug_spaxel = string(default='-1 -1 -1') # Default not used """ @@ -239,7 +239,7 @@ def process(self, input): # ________________________________________________________________________________ # If an offset file is provided do some basic checks on the file and its contents. # The offset list contains a matching list to the files in the association -# used in calspec3 (or offline cube building). +# used in calspec3 (for offline cube building). # Each row in the offset list contain a filename, ra offset and dec offset. # The offset list is an asdf file. self.offsets = None @@ -546,15 +546,15 @@ def read_user_input(self): # ________________________________________________________________________________ def check_offset_file(self): - """Read in an optional ra and dec offsets for each file. + """Read in an optional ra and dec offset for each file. Summary ---------- Check that is file is asdf file. - check the file has the correct format: + Check the file has the correct format using an local schema file. + The schema file, ifuoffset.schema.yaml, is located in the jwst/cube_build directory. For each file in the input assocation check that there is a corresponding file in the offset file. - Also check that each file in the offset list contain a ra offset and dec offset. """ @@ -565,7 +565,8 @@ def check_offset_file(self): af = asdf.open(self.offset_file, custom_schema=DATA_PATH/'ifuoffset.schema.yaml') except: schema_message = ('Validation Error for offset file. Fix the offset file. \n' + \ - 'The offset file needs to have the same number of elements the filename, raoffset and decoffset lists.\n' +\ + 'The offset file needs to have the same number of elements ' + \ + 'in the three lists: filename, raoffset and decoffset.\n' +\ 'The units need to provided and only arcsec is allowed.') raise Exception(schema_message) @@ -573,7 +574,9 @@ def check_offset_file(self): offset_filename = af['filename'] offset_ra = af['raoffset'] offset_dec = af['decoffset'] - + # Note: + # af['units'] is checked by the schema validation. It must be arcsec or a validation error occurs. + # check that all the file names in input_model are in the offset filename for model in self.input_models: file_check = model.meta.filename diff --git a/jwst/cube_build/ifu_cube.py b/jwst/cube_build/ifu_cube.py index c94b41fa69..3512e03542 100644 --- a/jwst/cube_build/ifu_cube.py +++ b/jwst/cube_build/ifu_cube.py @@ -1741,8 +1741,7 @@ def map_miri_pixel_to_sky(self, input_model, this_par1, subtract_background, if offsets is not None: raoffset, decoffset = self.find_ra_dec_offset(input_model.meta.filename) log.info("Ra and Dec offset (arc seconds) applied to file :%8.6f, %8.6f, %s", - raoffset.value, - decoffset.value, input_model.meta.filename) + raoffset.value, decoffset.value, input_model.meta.filename) # check if background sky matching as been done in mrs_imatch step # If it has not been subtracted and the background has not been @@ -2449,6 +2448,10 @@ def find_ra_dec_offset(self, filename): # ******************************************************************************** def offset_coord(self, ra, dec, raoffset, decoffset): + """ Given an ra,dec and ra offset and dec offset, use astropy SkyCoord functions + to apply the offsets + """ + coord = SkyCoord(ra, dec, unit='deg') coord_new = coord.spherical_offsets_by(raoffset, decoffset) From 45d15f1f009d6f44dc46c7131943f23b2d6f9cec Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 19 Sep 2024 10:00:46 -0700 Subject: [PATCH 34/37] fix comment --- jwst/cube_build/cube_build_step.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 33a349eb8c..6ee50d3f1b 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -240,7 +240,6 @@ def process(self, input): # If an offset file is provided do some basic checks on the file and its contents. # The offset list contains a matching list to the files in the association # used in calspec3 (for offline cube building). -# Each row in the offset list contain a filename, ra offset and dec offset. # The offset list is an asdf file. self.offsets = None From 04cbf6846b7fecb0d15155e55b69e8d9c3c45109 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 19 Sep 2024 10:12:36 -0700 Subject: [PATCH 35/37] cube_build_step.py --- jwst/cube_build/cube_build_step.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jwst/cube_build/cube_build_step.py b/jwst/cube_build/cube_build_step.py index 6ee50d3f1b..d9678cc7a4 100755 --- a/jwst/cube_build/cube_build_step.py +++ b/jwst/cube_build/cube_build_step.py @@ -583,7 +583,7 @@ def check_offset_file(self): continue else: af.close() - raise Exception('Error in offset file. A file in assocation is not found in offset list list %s', file_check) + raise ValueError('Error in offset file. A file in the assocation is not found in offset list %s', file_check) # check that all the lists have the same length len_file = len(offset_filename) @@ -591,7 +591,7 @@ def check_offset_file(self): len_dec = len(offset_dec) if (len_file != len_ra or len_ra != len_dec or len_file != len_dec): af.close() - raise Exception('Offset file error. The offset file does not have the same number of values for filename, offset_ra, offset_dec') + raise ValueError('The offset file does not have the same number of values for filename, raoffset, decoffset') offset_ra = offset_ra* units.arcsec offset_dec = offset_dec* units.arcsec From 8e8540eef554818134021356b1b7596b989479b5 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 19 Sep 2024 14:21:31 -0700 Subject: [PATCH 36/37] fix test test_offset.py ruff issue --- jwst/cube_build/tests/test_offset.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jwst/cube_build/tests/test_offset.py b/jwst/cube_build/tests/test_offset.py index 58149a5bd5..e14da0fdf3 100644 --- a/jwst/cube_build/tests/test_offset.py +++ b/jwst/cube_build/tests/test_offset.py @@ -3,7 +3,6 @@ """ import pytest -import sys import math import asdf from stdatamodels.jwst import datamodels @@ -159,8 +158,8 @@ def test2_offset_file_config(tmp_cwd, miri_ifushort_short_2files, offset_file): miri_ifushort_short_2files[0].meta.filename = 'test3.fits' step.offset_file = offset_file - with pytest.raises(Exception): - offsets = step.check_offset_file() + with pytest.raises(ValueError): + step.check_offset_file() def test_offset_file_units(tmp_cwd, miri_ifushort_short_2files, offset_file_arcmin): @@ -172,7 +171,7 @@ def test_offset_file_units(tmp_cwd, miri_ifushort_short_2files, offset_file_arcm step.offset_file = offset_file_arcmin with pytest.raises(Exception): - offsets = step.check_offset_file() + step.check_offset_file() def test_read_offset_file(miri_ifushort_short_2files, offset_file): """ Test offset file has been read in correctly""" From dfa83a7ae0056922067e6f258220b52985834114 Mon Sep 17 00:00:00 2001 From: jemorrison Date: Thu, 19 Sep 2024 14:25:11 -0700 Subject: [PATCH 37/37] fix test --- jwst/cube_build/tests/test_configuration.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/jwst/cube_build/tests/test_configuration.py b/jwst/cube_build/tests/test_configuration.py index 9a9eee11db..b98fa754fb 100644 --- a/jwst/cube_build/tests/test_configuration.py +++ b/jwst/cube_build/tests/test_configuration.py @@ -3,11 +3,8 @@ """ import pytest -import asdf from stdatamodels.jwst import datamodels -import astropy.units as u -from jwst.cube_build import CubeBuildStep from jwst.cube_build import cube_build from jwst.cube_build import file_table