예제 #1
0
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)
예제 #2
0
    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).')
예제 #3
0
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.')
예제 #4
0
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
예제 #6
0
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.')
예제 #7
0
    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))
예제 #8
0
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
예제 #9
0
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.')
예제 #10
0
    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))
예제 #11
0
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
예제 #12
0
    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))
예제 #13
0
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
예제 #14
0
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
예제 #15
0
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
예제 #16
0
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)
예제 #17
0
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.')
예제 #18
0
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.')
예제 #19
0
    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.')
예제 #20
0
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
예제 #21
0
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)