def build_position(hdf5_wcs, window, telescope): h = Header() # logging.error(hdf5_wcs) # logging.error(window) h['NAXIS1'] = window['X1'].data[telescope] - window['X0'].data[ telescope] + 1 h['NAXIS2'] = window['Y1'].data[telescope] - window['Y0'].data[ telescope] + 1 h['CRVAL1'] = hdf5_wcs['CRVAL1'].data[telescope] h['CRVAL2'] = hdf5_wcs['CRVAL2'].data[telescope] h['CRPIX1'] = hdf5_wcs['CRPIX1'].data[telescope] h['CRPIX2'] = hdf5_wcs['CRPIX2'].data[telescope] # h['CDELT1'] = 4.0 / 3600.0 # h['CDELT2'] = 4.0 / 3600.0 # # JJK - use only one of CD* or CDELT* - they are either conflicting or # redundant # h['CD1_1'] = hdf5_wcs['CD1_1'].data[telescope] h['CD1_2'] = hdf5_wcs['CD1_2'].data[telescope] h['CD2_1'] = hdf5_wcs['CD2_1'].data[telescope] h['CD2_2'] = hdf5_wcs['CD2_2'].data[telescope] w = wcs.WCS() bounds = CoordPolygon2D() result = w.calc_footprint(header=h) for ii in result: vertex = ValueCoord2D(ii[0], ii[1]) bounds.vertices.append(vertex) # ref_coord_x = RefCoord(mc.to_float(pix1), ra) # ref_coord_y = RefCoord(mc.to_float(pix2), dec) # coord = Coord2D(ref_coord_x, ref_coord_y) # dimension = Dimension2D(2, 2) # pixscale = 4.0 / 3600.0 => 4", per JJK # VLASS takes the cd?? approach shown here # function = CoordFunction2D(dimension=dimension, # ref_coord=coord, # cd11=4.0/3600.0, # cd12=0.0, # cd21=0.0, # cd22=4.0/3600.0) axis = CoordAxis2D(axis1=Axis(ctype='RA---TAN', cunit='deg'), axis2=Axis(ctype='DEC--TAN', cunit='deg'), error1=None, error2=None, range=None, bounds=bounds, function=None) return SpatialWCS(axis=axis, coordsys='FK5', equinox=2000.0, resolution=None)
def _update_energy(self, chunk): """Create SpectralWCS information using FITS headers, if available. If the WLEN and BANDPASS keyword values are set to the defaults, there is no energy information.""" self._logger.debug('Begin _update_energy') mc.check_param(chunk, Chunk) wlen = self._headers[0].get('WLEN') bandpass = self._headers[0].get('BANDPASS') if wlen is None or wlen < 0 or bandpass is None or bandpass < 0: chunk.energy = None chunk.energy_axis = None self._logger.debug( f'Setting chunk energy to None because WLEN {wlen} and ' f'BANDPASS {bandpass}' ) else: naxis = CoordAxis1D(Axis('WAVE', 'um')) start_ref_coord = RefCoord(0.5, self.get_start_ref_coord_val(0)) end_ref_coord = RefCoord(1.5, self.get_end_ref_coord_val(0)) naxis.range = CoordRange1D(start_ref_coord, end_ref_coord) chunk.energy = SpectralWCS( naxis, specsys='TOPOCENT', ssysobs='TOPOCENT', ssyssrc='TOPOCENT', bandpass_name=self._headers[0].get('FILTER'), ) chunk.energy_axis = None self._logger.debug('Setting chunk energy range (CoordRange1D).')
def build_chunk_time(chunk, header, name): """ :param chunk: CAOM2 Chunk instance for which to set time. :param header: FITS header with the keywords for value extraction. :param name: str for logging information only. :return: """ logging.debug(f'Begin build_chunk_time for {name}.') # DB 02-07-20 # time metadata comes from MJD_OBS and EXPTIME, it's not # an axis requiring cutout support exp_time = header.get('EXPTIME') mjd_obs = header.get('MJD-OBS') if exp_time is None or mjd_obs is None: chunk.time = None else: if chunk.time is None: coord_error = CoordError(syser=1e-07, rnder=1e-07) time_axis = CoordAxis1D(axis=Axis('TIME', 'd'), error=coord_error) chunk.time = TemporalWCS(axis=time_axis, timesys='UTC') ref_coord = RefCoord(pix=0.5, val=mjd_obs) chunk.time.axis.function = CoordFunction1D( naxis=1, delta=mc.convert_to_days(exp_time), ref_coord=ref_coord) chunk.time.exposure = exp_time chunk.time.resolution = mc.convert_to_days(exp_time) logging.debug(f'End build_chunk_time.')
def build_chunk_energy_range(chunk, filter_name, filter_md): """ Set a range axis for chunk energy using central wavelength and FWHM. Units are Angstroms. Axis is set to 4. :param chunk: Chunk to add a CoordRange1D Axis to :param filter_name: string to set to bandpassName :param filter_md: dict with a 'cw' and 'fwhm' value """ # If n_axis=1 (as I guess it will be for all but processes GRACES # spectra now?) that means crpix=0.5 and the corresponding crval would # central_wl - bandpass/2.0 (i.e. the minimum wavelength). It is fine # if you instead change crpix to 1.0. I guess since the ‘range’ of # one pixel is 0.5 to 1.5. cw = ac.FilterMetadataCache.get_central_wavelength(filter_md) fwhm = ac.FilterMetadataCache.get_fwhm(filter_md) if cw is not None and fwhm is not None: resolving_power = ac.FilterMetadataCache.get_resolving_power(filter_md) axis = CoordAxis1D(axis=Axis(ctype='WAVE', cunit='Angstrom')) ref_coord1 = RefCoord(0.5, cw - fwhm / 2.0) ref_coord2 = RefCoord(1.5, cw + fwhm / 2.0) axis.range = CoordRange1D(ref_coord1, ref_coord2) energy = SpectralWCS(axis=axis, specsys='TOPOCENT', ssyssrc=None, ssysobs=None, bandpass_name=filter_name, resolving_power=resolving_power) chunk.energy = energy
def _augment_artifact(obs_id, artifact): chunk = artifact.parts['0'].chunks[0] bounds = None exposure = None version = None reference = None found = False logging.debug(f'Scrape for time metadata for {obs_id}') global obs_metadata if obs_metadata is None: obs_metadata = scrape.retrieve_obs_metadata(obs_id) if obs_metadata is not None and len(obs_metadata) > 0: bounds, exposure = _build_time( obs_metadata.get('Observation Start'), obs_metadata.get('Observation End'), obs_metadata.get('On Source'), ) version = obs_metadata.get('Pipeline Version') reference = obs_metadata.get('reference') found = True else: logging.warning(f'Found no time metadata for {obs_id}') if found: time_axis = CoordAxis1D(Axis('TIME', 'd')) time_axis.bounds = bounds chunk.time = TemporalWCS(time_axis) chunk.time.exposure = exposure chunk.time_axis = None return version, reference else: return None, None
def _update_time(chunk, headers): """Create TemporalWCS information using FITS header information. This information should always be available from the file.""" logging.debug('Begin _update_time.') mc.check_param(chunk, Chunk) mjd_start = headers[0].get('MJD_STAR') mjd_end = headers[0].get('MJD_END') if mjd_start is None or mjd_end is None: mjd_start, mjd_end = ac.find_time_bounds(headers) if mjd_start is None or mjd_end is None: chunk.time = None logging.debug('Cannot calculate mjd_start {} or mjd_end {}'.format( mjd_start, mjd_end)) else: logging.debug('Calculating range with start {} and end {}.'.format( mjd_start, mjd_start)) start = RefCoord(0.5, mjd_start) end = RefCoord(1.5, mjd_end) time_cf = CoordFunction1D(1, headers[0].get('TEFF'), start) time_axis = CoordAxis1D(Axis('TIME', 'd'), function=time_cf) time_axis.range = CoordRange1D(start, end) chunk.time = TemporalWCS(time_axis) chunk.time.exposure = headers[0].get('TEFF') chunk.time.resolution = 0.1 chunk.time.timesys = 'UTC' chunk.time.trefpos = 'TOPOCENTER' chunk.time_axis = 4 logging.debug('Done _update_time.')
def test_function(self): # Polarization function is None, should not produce an error axis = Axis("STOKES", "cunit") axis_1d = CoordAxis1D(axis) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis function with naxis=1 naxis = int(1) delta = float(2.5) ref_coord = wcs.RefCoord(float(1.0), float(2.0)) axis_1d.function = CoordFunction1D(naxis, delta, ref_coord) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis function with naxis>1 naxis = int(3) delta = float(2.5) ref_coord = wcs.RefCoord(float(1.0), float(2.0)) axis_1d.function = CoordFunction1D(naxis, delta, ref_coord) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis function with invalid naxis=0 naxis = int(0) delta = float(2.5) ref_coord = wcs.RefCoord(float(1.0), float(2.0)) axis_1d.function = CoordFunction1D(naxis, delta, ref_coord) polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('Invalid naxis value' in str(ex.value))
def _build_chunk_energy(chunk, headers): # DB 18-09-19 # NEOSSat folks wanted the min/max wavelengths in the BANDPASS keyword to # be used as the upper/lower wavelengths. BANDPASS = ‘4000,9000’ so # ref_coord1 = RefCoord(0.5, 4000) and ref_coord2 = RefCoord(1.5, 9000). # The WAVELENG value is not used for anything since they opted to do it # this way. They interpret WAVELENG as being the wavelengh of peak # throughput of the system I think. min_wl, max_wl = _get_energy(headers[0]) axis = CoordAxis1D(axis=Axis(ctype='WAVE', cunit='um')) if min_wl is not None and max_wl is not None: ref_coord1 = RefCoord(0.5, min_wl) ref_coord2 = RefCoord(1.5, max_wl) axis.range = CoordRange1D(ref_coord1, ref_coord2) # DB 24-09-19 # If FILTER not in header, set filter_name = ‘CLEAR’ filter_name = headers[0].get('FILTER', 'CLEAR') # DB 24-09-19 # if wavelength IS None, wl = 0.6 microns, and resolving_power is # always determined. resolving_power = None wavelength = headers[0].get('WAVELENG', 6000) wl = wavelength / 1e4 # everything in microns resolving_power = wl / (max_wl - min_wl) energy = SpectralWCS(axis=axis, specsys='TOPOCENT', ssyssrc='TOPOCENT', ssysobs='TOPOCENT', bandpass_name=filter_name, resolving_power=resolving_power) chunk.energy = energy
def _update_energy(chunk, header, filter_name, obs_id): logging.debug(f'Begin _update_energy for {obs_id}.') # because the type for the axes are 'LINEAR', which isn't an energy type, # so can't use the WcsParser from caom2utils. disp_axis = header.get('DISPAXIS') naxis = header.get('NAXIS') if disp_axis is not None and naxis is not None and disp_axis <= naxis: axis = Axis(ctype='WAVE', cunit='Angstrom') coord_axis_1d = CoordAxis1D(axis) ref_coord = RefCoord( pix=header.get(f'CRPIX{disp_axis}'), val=header.get(f'CRVAL{disp_axis}'), ) fn = CoordFunction1D( naxis=header.get(f'NAXIS{disp_axis}'), delta=header.get(f'CD{disp_axis}_{disp_axis}'), ref_coord=ref_coord, ) coord_axis_1d.function = fn energy = SpectralWCS(axis=coord_axis_1d, specsys='TOPOCENT') energy.bandpass_name = filter_name # DB 07-08-20 # I think the best we can do is assume that a resolution element is 2 # pixels wide. So resolving power is the absolute value of # approximately CRVAL3/(2 * CD3_3) energy.resolving_power = abs( header.get(f'CRVAL{disp_axis}') / (2 * header.get(f'CD{disp_axis}_{disp_axis}'))) chunk.energy = energy chunk.energy_axis = disp_axis logging.debug('End _update_energy.')
def test_range(self): # Polarization range is None, should not produce an error axis = Axis("STOKES", "cunit") axis_1d = CoordAxis1D(axis) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis range contains valid positive values start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(9.9), float(10.1)) p_range = CoordRange1D(start, end) axis_1d.range = p_range polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis range contains valid negative values start = RefCoord(float(-8.1), float(-7.9)) end = RefCoord(float(-1.1), float(-0.9)) n_range = CoordRange1D(start, end) axis_1d.range = n_range polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis range contains invalid positive values start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(10.9), float(11.1)) p_range = CoordRange1D(start, end) axis_1d.range = p_range polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('11' in str(ex.value)) # Polarization axis range contains invalid negative values start = RefCoord(float(-9.1), float(-8.9)) end = RefCoord(float(-1.1), float(-0.9)) n_range = CoordRange1D(start, end) axis_1d.range = n_range polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('-9' in str(ex.value)) # Polarization axis range contains an invalid value (0) within a range start = RefCoord(float(-8.1), float(-7.9)) end = RefCoord(float(9.9), float(10.1)) range = CoordRange1D(start, end) axis_1d.range = range polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('0' in str(ex.value))
def _do_energy(artifact, science_file, working_dir, cfht_name): # PD slack 08-01-20 # espadons is a special case because using bounds allows one to # define "tiles" and then the SODA cutout service can extract the # subset of tiles that overlap the desired region. That's the best # that can be done because it is not possible to create a # CoordFunction1D to say what the wavelength of each pixel is # # If the coverage had significant gaps (eg SCUBA or SCUBA2 from # JCMT) then the extra detail in bounds would enable better # discovery (as the gaps would be captured in the plane metadata). # In the case of espadons I don't think the gaps are significant # (iirc, espadons is an eschelle spectrograph but I don't recall # whether the discontinuity between eschelle was a small gap or an # overlap) # # So: bounds provides more detail and it can in principle improve # data discovery (if gaps) and enable extraction of subsections of # the spectrum via the SODA service. Espadons was one of the use # cases that justified having bounds there # SF slack 08-01-20 # We need the information that is contained in bounds. Gaps need # to be captured. So keep bounds. If you decide to remove range, # then advanced users would have to dig in the info to understand # range is first and last bounds. # read in the complete fits file, including the data fqn = f'{working_dir}/{science_file}' logging.info(f'Reading ESPaDOnS energy data from {fqn}.') hdus = ac.read_fits_data(fqn) wave = hdus[0].data[0, :] axis = Axis('WAVE', 'nm') coord_bounds = ac.build_chunk_energy_bounds(wave, axis) coord_axis = CoordAxis1D(axis=axis, bounds=coord_bounds) params = {'header': hdus[0].header, 'uri': artifact.uri} resolving_power = main_app.get_espadons_energy_resolving_power(params) chunk = artifact.parts['0'].chunks[0] chunk.energy = SpectralWCS(coord_axis, specsys='TOPOCENT', ssyssrc='TOPOCENT', resolving_power=resolving_power) chunk.energy_axis = 1 chunk.naxis = hdus[0].header.get('NAXIS') if (chunk.naxis is not None and chunk.naxis == 2 and chunk.observable is not None): chunk.observable_axis = 2 chunk.position_axis_1 = None chunk.position_axis_2 = None chunk.time_axis = None chunk.custom_axis = None if cfht_name.suffix != 'p': chunk.polarization_axis = None hdus.close() return 1
def test_bounds(self): # Polarization bounds is None, should not produce an error axis = Axis("STOKES", "cunit") axis_1d = CoordAxis1D(axis) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis bounds contains one valid range start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(9.9), float(10.1)) p_range = CoordRange1D(start, end) samples = caom_util.TypedList(CoordRange1D, p_range) axis_1d.bounds = CoordBounds1D(samples) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis bounds contains more than one valid range start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(9.9), float(10.1)) p_range = CoordRange1D(start, end) start = RefCoord(float(-8.1), float(-7.9)) end = RefCoord(float(-1.1), float(-0.9)) n_range = CoordRange1D(start, end) samples = caom_util.TypedList(CoordRange1D, p_range, n_range) axis_1d.bounds = CoordBounds1D(samples) polarization = PolarizationWCS(axis_1d) wcsvalidator._validate_polarization_wcs(polarization) # Polarization axis bounds contains one invalid range start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(10.9), float(11.1)) p_range = CoordRange1D(start, end) samples = caom_util.TypedList(CoordRange1D, p_range) axis_1d.bounds = CoordBounds1D(samples) polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('11' in str(ex.value)) # Polarization axis bounds contains more than one invalid range start = RefCoord(float(0.9), float(1.1)) end = RefCoord(float(9.9), float(10.1)) p_range = CoordRange1D(start, end) start = RefCoord(float(-9.1), float(-8.9)) end = RefCoord(float(-1.1), float(-0.9)) n_range = CoordRange1D(start, end) samples = caom_util.TypedList(CoordRange1D, p_range, n_range) axis_1d.bounds = CoordBounds1D(samples) polarization = PolarizationWCS(axis_1d) with pytest.raises(InvalidWCSError) as ex: wcsvalidator._validate_polarization_wcs(polarization) assert ('Invalid Polarization WCS' in str(ex.value)) assert ('-9' in str(ex.value))
def build_temporal_wcs_append_sample(temporal_wcs, lower, upper): """All the CAOM entities for building a TemporalWCS instance with a bounds definition, or appending a sample, in one function. """ if temporal_wcs is None: samples = TypedList(CoordRange1D, ) bounds = CoordBounds1D(samples=samples) temporal_wcs = TemporalWCS(axis=CoordAxis1D(axis=Axis('TIME', 'd'), bounds=bounds), timesys='UTC') start_ref_coord = RefCoord(pix=0.5, val=lower) end_ref_coord = RefCoord(pix=1.5, val=upper) sample = CoordRange1D(start_ref_coord, end_ref_coord) temporal_wcs.axis.bounds.samples.append(sample) return temporal_wcs
def build_energy(): # units are nm min = 400 max = 800 central_wl = (min + max) / 2.0 # = 1200.0 / 2.0 == 600.0 nm fwhm = (max - min) ref_coord1 = RefCoord(0.5, central_wl - fwhm / 2.0) # == 100.0 nm ref_coord2 = RefCoord(1.5, central_wl + fwhm / 2.0) # == 500.0 nm axis = CoordAxis1D(axis=Axis(ctype='WAVE', cunit='nm')) axis.range = CoordRange1D(ref_coord1, ref_coord2) energy = SpectralWCS(axis=axis, specsys='TOPOCENT', ssyssrc='TOPOCENT', ssysobs='TOPOCENT', bandpass_name='CLEAR') return energy
def _augment_artifact(obs_id, artifact, csv_file): chunk = artifact.parts['0'].chunks[0] bounds = None exposure = None version = None reference = None for ii in csv_file: if obs_id in ii: bounds, exposure = _build_time(ii) version = ii[1].strip() reference = ii[2].strip() break time_axis = CoordAxis1D(Axis('TIME', 'd')) time_axis.bounds = bounds chunk.time = TemporalWCS(time_axis) chunk.time.exposure = exposure return version, reference
def build_time(seconds, microseconds): mjd_start = AstroTime(seconds, format='unix') mjd_start.format = 'mjd' start = RefCoord(0.5, mjd_start.value) end_ms = seconds + (microseconds / 1e7) mjd_end = AstroTime(end_ms, format='unix') mjd_end.format = 'mjd' end = RefCoord(1.5, mjd_end.value) axis = CoordAxis1D(axis=Axis(ctype='TIME', cunit='d')) axis.range = CoordRange1D(start, end) return TemporalWCS(axis=axis, timesys='UTC', trefpos=None, mjdref=None, exposure=(microseconds / 1e7), resolution=None)
def _update_ngvs_time(chunk, provenance, obs_id): logging.debug(f'Begin _update_ngvs_time for {obs_id}') if (chunk is not None and provenance is not None and len(provenance.inputs) > 0): # bounds = ctor config = mc.Config() config.get_executors() subject = mc.define_subject(config) client = CAOM2RepoClient( subject, config.logging_level, 'ivo://cadc.nrc.ca/ams') metrics = mc.Metrics(config) bounds = CoordBounds1D() min_date = 0 max_date = sys.float_info.max exposure = 0 for entry in provenance.inputs: ip_obs_id, ip_product_id = mc.CaomName.decompose_provenance_input( entry.uri) logging.info(f'Retrieving provenance metadata for {ip_obs_id}.') ip_obs = mc.repo_get(client, 'CFHT', ip_obs_id, metrics) if ip_obs is not None: ip_plane = ip_obs.planes.get(ip_product_id) if (ip_plane is not None and ip_plane.time is not None and ip_plane.time.bounds is not None): bounds.samples.append(CoordRange1D( RefCoord(pix=0.5, val=ip_plane.time.bounds.lower), RefCoord(pix=1.5, val=ip_plane.time.bounds.upper))) min_date = min(ip_plane.time.bounds.lower, min_date) max_date = max(ip_plane.time.bounds.upper, max_date) exposure += ip_plane.time.exposure axis = Axis(ctype='TIME', cunit='d') time_axis = CoordAxis1D(axis=axis, error=None, range=None, bounds=bounds, function=None) temporal_wcs = TemporalWCS(axis=time_axis, timesys=None, trefpos=None, mjdref=None, exposure=mc.to_float(exposure), resolution=None) chunk.time = temporal_wcs logging.debug(f'End _update_ngvs_time.')
def _update_time(part, chunk, header, obs_id): logging.debug(f'Begin _update_time for {obs_id} part {part.name}.') # DB 02-07-20 # time metadata comes from MJD_OBS and EXPTIME, it's not # an axis requiring cutout support exp_time = header.get('EXPTIME') mjd_obs = header.get('MJD_OBS') if exp_time is None or mjd_obs is None: chunk.time = None else: if chunk.time is None: coord_error = CoordError(syser=1e-07, rnder=1e-07) time_axis = CoordAxis1D(axis=Axis('TIME', 'd'), error=coord_error) chunk.time = TemporalWCS(axis=time_axis, timesys='UTC') ref_coord = RefCoord(pix=0.5, val=mjd_obs) chunk.time.axis.function = CoordFunction1D( naxis=1, delta=mc.convert_to_days(exp_time), ref_coord=ref_coord) chunk.time.exposure = float(exp_time) chunk.time.resolution = mc.convert_to_days(exp_time) logging.debug(f'End _update_time.')
def _update_time(self, chunk, obs_id): """Create TemporalWCS information using FITS header information. This information should always be available from the file.""" self._logger.debug('Begin _update_time.') mc.check_param(chunk, Chunk) mjd_start = self._headers[0].get('MJD_STAR') mjd_end = self._headers[0].get('MJD_END') if mjd_start is None or mjd_end is None: mjd_start, mjd_end = ac.find_time_bounds(self._headers) if mjd_start is None or mjd_end is None: chunk.time = None self._logger.debug( f'Cannot calculate MJD_STAR {mjd_start} or ' f'MDJ_END' f' {mjd_end}' ) elif mjd_start == 'NaN' or mjd_end == 'NaN': raise mc.CadcException( f'Invalid time values MJD_STAR {mjd_start} or MJD_END ' f'{mjd_end} for {obs_id}, stopping ingestion.' ) else: self._logger.debug( f'Calculating range with start {mjd_start} and end {mjd_end}.' ) start = RefCoord(0.5, mjd_start) end = RefCoord(1.5, mjd_end) time_cf = CoordFunction1D(1, self._headers[0].get('TEFF'), start) time_axis = CoordAxis1D(Axis('TIME', 'd'), function=time_cf) time_axis.range = CoordRange1D(start, end) chunk.time = TemporalWCS(time_axis) chunk.time.exposure = self._headers[0].get('TEFF') chunk.time.resolution = 0.1 chunk.time.timesys = 'UTC' chunk.time.trefpos = 'TOPOCENTER' chunk.time_axis = None self._logger.debug('Done _update_time.')
def test_augment_artifact_bounds_range_from_blueprint(): test_blueprint = ObsBlueprint(energy_axis=1, time_axis=2, polarization_axis=3, position_axes=(4, 5)) test_blueprint.set('Chunk.energy.axis.range.start.pix', '145.0') test_blueprint.set('Chunk.energy.axis.range.start.val', '-60000.0') test_blueprint.set('Chunk.energy.axis.range.end.pix', '-824.46002') test_blueprint.set('Chunk.energy.axis.range.end.val', '1') test_blueprint.set('Chunk.time.axis.range.start.pix', '145.0') test_blueprint.set('Chunk.time.axis.range.start.val', '-60000.0') test_blueprint.set('Chunk.time.axis.range.end.pix', '-824.46002') test_blueprint.set('Chunk.time.axis.range.end.val', '1') test_blueprint.set('Chunk.polarization.axis.range.start.pix', '145.0') test_blueprint.set('Chunk.polarization.axis.range.start.val', '-60000.0') test_blueprint.set('Chunk.polarization.axis.range.end.pix', '-824.46002') test_blueprint.set('Chunk.polarization.axis.range.end.val', '1') test_blueprint.set('Chunk.position.axis.range.start.coord1.pix', '145.0') test_blueprint.set('Chunk.position.axis.range.start.coord1.val', '-60000.0') test_blueprint.set('Chunk.position.axis.range.end.coord1.pix', '-824.46002') test_blueprint.set('Chunk.position.axis.range.end.coord1.val', '1') test_blueprint.set('Chunk.position.axis.range.start.coord2.pix', '145.0') test_blueprint.set('Chunk.position.axis.range.start.coord2.val', '-60000.0') test_blueprint.set('Chunk.position.axis.range.end.coord2.pix', '-824.46002') test_blueprint.set('Chunk.position.axis.range.end.coord2.val', '1') test_fitsparser = FitsParser(sample_file_4axes, test_blueprint, uri='ad:TEST/test_blueprint') test_chunk = Chunk() test_chunk.energy = SpectralWCS(CoordAxis1D(Axis('WAVE', 'm')), 'TOPOCENT') test_chunk.time = TemporalWCS(CoordAxis1D(Axis('TIME', 'd'))) test_chunk.polarization = PolarizationWCS(CoordAxis1D(Axis('STOKES'))) test_chunk.position = SpatialWCS( CoordAxis2D(Axis('RA', 'deg'), Axis('DEC', 'deg'))) test_fitsparser._try_range_with_blueprint(test_chunk, 0) assert test_chunk.energy.axis.range is not None, \ 'chunk.energy.axis.range should be declared' assert test_chunk.time.axis.range is not None, \ 'chunk.time.axis.range should be declared' assert test_chunk.polarization.axis.range is not None, \ 'chunk.polarization.axis.range should be declared' assert test_chunk.position.axis.range is not None, \ 'chunk.position.axis.range should be declared' ex = _get_from_str_xml(EXPECTED_ENERGY_RANGE_BOUNDS_XML, ObservationReader()._get_spectral_wcs, 'energy') assert ex is not None, \ 'energy string from expected output should be declared' result = get_differences(ex, test_chunk.energy) assert result is None ex = _get_from_str_xml(EXPECTED_TIME_RANGE_BOUNDS_XML, ObservationReader()._get_temporal_wcs, 'time') assert ex is not None, \ 'time string from expected output should be declared' result = get_differences(ex, test_chunk.time) assert result is None ex = _get_from_str_xml(EXPECTED_POL_RANGE_BOUNDS_XML, ObservationReader()._get_polarization_wcs, 'polarization') assert ex is not None, \ 'polarization string from expected output should be declared' result = get_differences(ex, test_chunk.polarization) assert result is None ex = _get_from_str_xml(EXPECTED_POS_RANGE_BOUNDS_XML, ObservationReader()._get_spatial_wcs, 'position') assert ex is not None, \ 'position string from expected output should be declared' result = get_differences(ex, test_chunk.position) assert result is None
def _update_from_comment(observation, phangs_name, headers): # From ER: 04-03-21 # COMMENT Produced with PHANGS-ALMA pipeline version 4.0 Build 935 # - Provenance.version # COMMENT Galaxy properties from PHANGS sample table version 1.6 # COMMENT Calibration Level 4 (ANALYSIS_PRODUCT) # - Calibration level (either 3 or 4) # COMMENT PHANGS-ALMA Public Release 1 # - Provenance.project = PHANGS-ALMA # COMMENT Generated by the Physics at High Angular resolution # COMMENT in nearby GalaxieS (PHANGS) collaboration # - Provenance.organization = PHANGS # COMMENT Canonical Reference: Leroy et al. (2021), ApJ, Submitted # - Update to reference when accepted # COMMENT Release generated at 2021-03-04T07:28:10.245340 # - Provenance.lastExecuted # COMMENT Data from ALMA Proposal ID: 2017.1.00886.L # - Proposal.proposalID # COMMENT ALMA Proposal PI: Schinnerer, Eva # - Proposal.pi_name # COMMENT Observed in MJD interval [58077.386275,58081.464121] # COMMENT Observed in MJD interval [58290.770032,58365.629222] # COMMENT Observed in MJD interval [58037.515807,58047.541173] # COMMENT Observed in MJD interval [58353.589805,58381.654757] # COMMENT Observed in MJD interval [58064.3677,58072.458597] # COMMENT Observed in MJD interval [58114.347649,58139.301879] chunk = None for plane in observation.planes.values(): if plane.product_id != phangs_name.product_id: continue if plane.provenance is None: plane.provenance = Provenance(name='PHANGS-ALMA pipeline') for artifact in plane.artifacts.values(): if artifact.uri != phangs_name.file_uri: continue for part in artifact.parts.values(): chunk = part.chunks[0] break for entry in headers[0].get('COMMENT'): if 'pipeline version ' in entry: plane.provenance.version = entry.split(' version ')[1] elif 'Calibration Level' in entry: level = entry.split()[2] if level == '4': plane.calibration_level = CalibrationLevel.ANALYSIS_PRODUCT elif 'PHANGS-ALMA Public Release' in entry: plane.provenance.project = 'PHANGS-ALMA' elif 'in nearby GalaxieS (PHANGS) collaboration' in entry: plane.provenance.organization = 'PHANGS' elif 'Release generated at ' in entry: plane.provenance.last_executed = mc.make_time_tz( entry.split(' at ')[1]) elif 'Data from ALMA Proposal ID:' in entry: observation.proposal = Proposal(entry.split(':')[1].strip()) elif 'Canonical Reference: ' in entry: plane.provenance.producer = entry.split(': ')[1] elif 'ALMA Proposal PI:' in entry: observation.proposal.pi_name = entry.split(': ')[1] elif 'Observed in MJD interval ' in entry: if chunk is not None: bits = entry.split()[4].split(',') start_ref_coord = RefCoord( 0.5, mc.to_float(bits[0].replace('[', ''))) end_ref_coord = RefCoord( 1.5, mc.to_float(bits[1].replace(']', ''))) sample = CoordRange1D(start_ref_coord, end_ref_coord) if chunk.time is None: coord_bounds = CoordBounds1D() axis = CoordAxis1D(axis=Axis('TIME', 'd')) chunk.time = TemporalWCS(axis, timesys='UTC') chunk.time.axis.bounds = coord_bounds chunk.time.axis.bounds.samples.append(sample)