def fitswcs_to_gwcs(hdr): """ Create and return a gWCS object from a FITS header. If it can't construct one, it should quietly return None. """ # Type of CoordinateFrame to construct for a FITS keyword frame_mapping = {'WAVE': cf.SpectralFrame} # coordinate names for CelestialFrame coordinate_outputs = {'alpha_C', 'delta_C'} # transform = gw.make_fitswcs_transform(hdr) try: transform = make_fitswcs_transform(hdr) except Exception as e: return None outputs = transform.outputs naxes = transform.n_inputs axes_names = ('x', 'y', 'z', 'u', 'v', 'w')[:naxes] in_frame = cf.CoordinateFrame(naxes=naxes, axes_type=['SPATIAL'] * naxes, axes_order=tuple(range(naxes)), name="pixels", axes_names=axes_names, unit=[u.pix] * naxes) out_frames = [] for i, output in enumerate(outputs): unit_name = hdr.get(f'CUNIT{i+1}') try: unit = u.Unit(unit_name) except TypeError: unit = None try: frame = frame_mapping[output[:4].upper()](axes_order=(i,), unit=unit, axes_names=(output,), name=output) except KeyError: if output in coordinate_outputs: continue frame = cf.CoordinateFrame(naxes=1, axes_type=("SPATIAL",), axes_order=(i,), unit=unit, axes_names=(output,), name=output) out_frames.append(frame) if coordinate_outputs.issubset(outputs): frame_name = hdr.get('RADESYS') or hdr.get('RADECSYS') # FK5, etc. try: ref_frame = getattr(coord, frame_name)() # TODO? Work out how to stuff EQUINOX and OBS-TIME into the frame except (AttributeError, TypeError): ref_frame = None axes_order = (outputs.index('alpha_C'), outputs.index('delta_C')) # Call it 'world' if there are no other axes, otherwise 'sky' name = 'SKY' if len(outputs) > 2 else 'world' cel_frame = cf.CelestialFrame(reference_frame=ref_frame, name=name, axes_order=axes_order) out_frames.append(cel_frame) out_frame = (out_frames[0] if len(out_frames) == 1 else cf.CompositeFrame(out_frames, name='world')) return gWCS([(in_frame, transform), (out_frame, None)])
def identity_gwcs(): """ A simple 1-1 gwcs that converts from pixels to arcseconds Note this WCS does not have a correct axis correlation matrix. """ identity = m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply( 1 * u.arcsec / u.pixel) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01"), unit=(u.arcsec, u.arcsec), axis_physical_types=("custom:pos.helioprojective.lat", "custom:pos.helioprojective.lon")) detector_frame = cf.CoordinateFrame(name="detector", naxes=2, axes_order=(0, 1), axes_type=("pixel", "pixel"), axes_names=("x", "y"), unit=(u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=identity, output_frame=sky_frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) tabular_gwcs = GWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) return tabular_gwcs
def identity_gwcs_3d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (TwoDScale(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01"), axes_names=("longitude", "latitude"), unit=(u.arcsec, u.arcsec), axis_physical_types=("custom:pos.helioprojective.lon", "custom:pos.helioprojective.lat")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm, axes_names=("wavelength", )) frame = cf.CompositeFrame([sky_frame, wave_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20, 30) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def create_fitswcs(inp, input_frame=None): if isinstance(inp, DataModel): wcsinfo = wcsinfo_from_model(inp) wavetable = None spatial_axes, spectral_axes, unknown = gwutils.get_axes(wcsinfo) if spectral_axes: sp_axis = spectral_axes[0] if wcsinfo['CTYPE'][sp_axis] == 'WAVE-TAB': wavetable = inp.wavetable transform = fitswcs_transform_from_model(wcsinfo, wavetable=wavetable) output_frame = frame_from_model(wcsinfo) else: raise TypeError( "Input is expected to be a DataModel instance or a FITS file.") if input_frame is None: wcsaxes = wcsinfo['WCSAXES'] if wcsaxes == 2: input_frame = cf.Frame2D(name="detector") elif wcsaxes == 3: input_frame = cf.CoordinateFrame( name="detector", naxes=3, axes_order=(0, 1, 2), unit=(u.pix, u.pix, u.pix), axes_type=["SPATIAL", "SPATIAL", "SPECTRAL"], axes_names=('x', 'y', 'z'), axis_physical_types=None) else: raise TypeError( f"WCSAXES is expected to be 2 or 3, instead it is {wcsaxes}") pipeline = [(input_frame, transform), (output_frame, None)] wcsobj = wcs.WCS(pipeline) return wcsobj
def build_pixel_frame(header): """ Given a header, build the input `gwcs.coordinate_frames.CoordinateFrame` object describing the pixel frame. Parameters ---------- header : `dict` A fits header. Returns ------- pixel_frame : `gwcs.coordinate_frames.CoordinateFrame` The pixel frame. """ axes_types = [header[f'DTYPE{n}'] for n in range(1, header['DNAXIS'] + 1)] return cf.CoordinateFrame(naxes=header['DNAXIS'], axes_type=axes_types, axes_order=range(header['DNAXIS']), unit=[u.pixel] * header['DNAXIS'], axes_names=[ header[f'DPNAME{n}'] for n in range(1, header['DNAXIS'] + 1) ], name='pixel')
def generate_s3d_wcs(): """ create a fake gwcs for a cube """ # create input /output frames detector = cf.CoordinateFrame(name='detector', axes_order=(0,1,2), axes_names=['x', 'y', 'z'], axes_type=['spatial', 'spatial', 'spatial'], naxes=3, unit=['pix', 'pix', 'pix']) sky = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_names=("RA", "DEC")) spec = cf.SpectralFrame(name='spectral', unit=['um'], axes_names=['wavelength'], axes_order=(2,)) world = cf.CompositeFrame(name="world", frames=[sky, spec]) # create fake transform to at least get a bounding box # for the s3d jwst loader # shape 30,10,10 (spec, y, x) crpix1, crpix2, crpix3 = 5, 5, 15 # (x, y, spec) crval1, crval2, crval3 = 1, 1, 1 cdelt1, cdelt2, cdelt3 = 0.01, 0.01, 0.05 shift = models.Shift(-crpix2) & models.Shift(-crpix1) scale = models.Multiply(cdelt2) & models.Multiply(cdelt1) proj = models.Pix2Sky_TAN() skyrot = models.RotateNative2Celestial(crval2, 90 + crval1, 180) celestial = shift | scale | proj | skyrot wave_model = models.Shift(-crpix3) | models.Multiply(cdelt3) | models.Shift(crval3) transform = models.Mapping((2, 0, 1)) | celestial & wave_model | models.Mapping((1, 2, 0)) # bounding box based on shape (30,10,10) in test transform.bounding_box = ((0, 29), (0, 9), (0, 9)) # create final wcs pipeline = [(detector, transform), (world, None)] return WCS(pipeline)
def identity_gwcs_4d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel) & m.Multiply(1 * u.nm / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm) time_frame = cf.TemporalFrame(axes_order=(3, ), unit=u.s) frame = cf.CompositeFrame([sky_frame, wave_frame, time_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "z", "s"), unit=(u.pix, u.pix, u.pix, u.pix)) return gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame)
def _generate_generic_frame(naxes, unit, names=None, physical_types=None): """ Generate a simple frame, where all axes have the same type and unit. """ axes_order = tuple(range(naxes)) name = None axes_type = "CUSTOM" if isinstance(unit, (u.Unit, u.IrreducibleUnit, u.CompositeUnit)): unit = tuple([unit] * naxes) if all([u.m.is_equivalent(un) for un in unit]): axes_type = "SPATIAL" if all([u.pix.is_equivalent(un) for un in unit]): name = "PixelFrame" axes_type = "PIXEL" axes_type = tuple([axes_type] * naxes) return cf.CoordinateFrame(naxes, axes_type, axes_order, unit=unit, axes_names=names, name=name, axis_physical_types=physical_types)
def tabular_wcs(xarray): coordinateframe = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL',), axes_order=(0,)) specframe = cf.SpectralFrame(unit=xarray.unit, axes_order=(0,)) transform = Tabular1D(np.arange(len(xarray)), xarray.value) tabular_gwcs = gwcs.wcs.WCS([(coordinateframe, transform), (specframe, None)]) return tabular_gwcs
def gwcs_1d(): detector_frame = cf.CoordinateFrame( name="detector", naxes=1, axes_order=(0, ), axes_type=("pixel"), axes_names=("x"), unit=(u.pix)) spec_frame = cf.SpectralFrame(name="spectral", axes_order=(2, ), unit=u.nm) return WCS(forward_transform=Identity(1), input_frame=detector_frame, output_frame=spec_frame)
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ orig_array = u.Quantity(array) # TODO: Input arrays must be strictly ascending. This is not always the # case for a spectral axis (e.g. when in frequency space). Thus, we # convert to wavelength to create the wcs. if orig_array.unit.physical_type != 'length' and \ orig_array.unit.is_equivalent(u.AA, equivalencies=u.spectral()): array = orig_array.to(u.AA, equivalencies=u.spectral()) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) class SpectralGWCS(GWCS): def pixel_to_world(self, *args, **kwargs): if orig_array.unit == '': return u.Quantity(super().pixel_to_world_values( *args, **kwargs)) return super().pixel_to_world(*args, **kwargs).to( orig_array.unit, equivalencies=u.spectral()) tabular_gwcs = SpectralGWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) # Store the intended unit from the origin input array # tabular_gwcs._input_unit = orig_array.unit return tabular_gwcs
def test_composite_many_base_frame(): q_frame_1 = cf.CoordinateFrame(name='distance', axes_order=(0, ), naxes=1, axes_type="SPATIAL", unit=(u.m, )) q_frame_2 = cf.CoordinateFrame(name='distance', axes_order=(1, ), naxes=1, axes_type="SPATIAL", unit=(u.m, )) frame = cf.CompositeFrame([q_frame_1, q_frame_2]) wao_classes = frame._world_axis_object_classes assert len(wao_classes) == 2 assert not set(wao_classes.keys()).difference({"SPATIAL", "SPATIAL1"}) wao_components = frame._world_axis_object_components assert len(wao_components) == 2 assert not {c[0] for c in wao_components}.difference({"SPATIAL", "SPATIAL1"})
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ orig_array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) # If our spectral axis is in descending order, we have to flip the lookup # table to be ascending in order for world_to_pixel to work. if len(array) == 0 or array[-1] > array[0]: forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) else: forward_transform.inverse = SpectralTabular1D(array[::-1], lookup_table=np.arange( len(array))[::-1]) class SpectralGWCS(GWCS): def pixel_to_world(self, *args, **kwargs): if orig_array.unit == '': return u.Quantity(super().pixel_to_world_values( *args, **kwargs)) return super().pixel_to_world(*args, **kwargs).to( orig_array.unit, equivalencies=u.spectral()) tabular_gwcs = SpectralGWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) # Store the intended unit from the origin input array # tabular_gwcs._input_unit = orig_array.unit return tabular_gwcs
def gwcs_3d(): detector_frame = cf.CoordinateFrame( name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) sky_frame = cf.CelestialFrame(reference_frame=Helioprojective(), name='hpc') spec_frame = cf.SpectralFrame(name="spectral", axes_order=(2, ), unit=u.nm) out_frame = cf.CompositeFrame(frames=(sky_frame, spec_frame)) return WCS(forward_transform=spatial_like_model(), input_frame=detector_frame, output_frame=out_frame)
def _create_fake_data(object_name): from astropy.table import Table astrofaker = pytest.importorskip('astrofaker') wavelength, flux = _get_spectrophotometric_data(object_name) wavecal = { 'degree': 1, 'domain': [0., wavelength.size - 1], 'c0': wavelength.mean(), 'c1': wavelength.mean() / 2, } wave_model = models.Chebyshev1D(**wavecal) wave_model.inverse = astromodels.make_inverse_chebyshev1d( wave_model, rms=0.01, max_deviation=0.03) hdu = fits.ImageHDU() hdu.header['CCDSUM'] = "1 1" hdu.data = flux[np.newaxis, :] # astrofaker needs 2D data _ad = astrofaker.create('GMOS-S') _ad.add_extension(hdu, pixel_scale=1.0) _ad[0].data = _ad[0].data.ravel() _ad[0].mask = np.zeros(_ad[0].data.size, dtype=np.uint16) # ToDo Requires mask _ad[0].variance = np.ones_like(_ad[0].data) # ToDo Requires Variance in_frame = cf.CoordinateFrame(naxes=1, axes_type=['SPATIAL'], axes_order=(0, ), unit=u.pix, axes_names=('x', ), name='pixels') out_frame = cf.SpectralFrame(unit=u.nm, name='world') _ad[0].wcs = gWCS([(in_frame, wave_model), (out_frame, None)]) _ad[0].hdr.set('NAXIS', 1) _ad[0].phu.set('OBJECT', object_name) _ad[0].phu.set('EXPTIME', 1.) _ad[0].hdr.set('BUNIT', "electron") assert _ad.object() == object_name assert _ad.exposure_time() == 1 return _ad
def identity_gwcs(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply( 1 * u.arcsec / u.pixel) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01")) detector_frame = cf.CoordinateFrame(name="detector", naxes=2, axes_order=(0, 1), axes_type=("pixel", "pixel"), axes_names=("x", "y"), unit=(u.pix, u.pix)) return gwcs.wcs.WCS(forward_transform=identity, output_frame=sky_frame, input_frame=detector_frame)
def from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) forward_transform = Tabular1D(np.arange(len(array)), array.value) forward_transform.inverse = Tabular1D(array.value, np.arange(len(array))) tabular_gwcs = gwcs.wcs.WCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) return WCSWrapper(wcs=tabular_gwcs)
def identity_gwcs_4d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (TwoDScale(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel) & m.Multiply(1 * u.s / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01"), unit=(u.arcsec, u.arcsec), axis_physical_types=("custom:pos.helioprojective.lon", "custom:pos.helioprojective.lat")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm) time_frame = cf.TemporalFrame(Time("2020-01-01T00:00", format="isot", scale="utc"), axes_order=(3, ), unit=u.s) frame = cf.CompositeFrame([sky_frame, wave_frame, time_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "z", "s"), unit=(u.pix, u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20, 30, 40) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def identity_gwcs_5d_stokes(identity_gwcs_4d): stokes_frame = cf.StokesFrame(axes_order=(4, )) stokes_model = generate_lookup_table([0, 1, 2, 3] * u.one, interpolation='nearest') transform = identity_gwcs_4d.forward_transform frame = cf.CompositeFrame(identity_gwcs_4d.output_frame.frames + [stokes_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=5, axes_order=(0, 1, 2, 3, 4), axes_type=("pixel", "pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "z", "t", "s"), unit=(u.pix, u.pix, u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=transform & stokes_model, output_frame=frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20, 30, 40, 4) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def fitswcs_to_gwcs(hdr): """ Create and return a gWCS object from a FITS header. If it can't construct one, it should quietly return None. """ # coordinate names for CelestialFrame coordinate_outputs = {'alpha_C', 'delta_C'} # transform = gw.make_fitswcs_transform(hdr) try: transform = make_fitswcs_transform(hdr) except Exception as e: return None outputs = transform.outputs wcs_info = read_wcs_from_header(hdr) naxes = transform.n_inputs axes_names = ('x', 'y', 'z', 'u', 'v', 'w')[:naxes] in_frame = cf.CoordinateFrame(naxes=naxes, axes_type=['SPATIAL'] * naxes, axes_order=tuple(range(naxes)), name="pixels", axes_names=axes_names, unit=[u.pix] * naxes) out_frames = [] for i, output in enumerate(outputs): unit_name = wcs_info["CUNIT"][i] try: unit = u.Unit(unit_name) except TypeError: unit = None try: frame_type = output[:4].upper() frame_info = frame_mapping[frame_type] except KeyError: if output in coordinate_outputs: continue frame = cf.CoordinateFrame(naxes=1, axes_type=("SPATIAL", ), axes_order=(i, ), unit=unit, axes_names=(output, ), name=output) else: frame = frame_info.cls(axes_order=(i, ), unit=unit, axes_names=(frame_type, ), name=frame_info.description) out_frames.append(frame) if coordinate_outputs.issubset(outputs): frame_name = wcs_info["RADESYS"] # FK5, etc. axes_names = None try: ref_frame = getattr(coord, frame_name)() # TODO? Work out how to stuff EQUINOX and OBS-TIME into the frame except (AttributeError, TypeError): # TODO: Replace quick fix as gWCS doesn't recognize GAPPT if frame_name == "GAPPT": ref_frame = coord.FK5() else: ref_frame = None axes_names = ('lon', 'lat') axes_order = (outputs.index('alpha_C'), outputs.index('delta_C')) # Call it 'world' if there are no other axes, otherwise 'sky' name = 'SKY' if len(outputs) > 2 else 'world' cel_frame = cf.CelestialFrame(reference_frame=ref_frame, name=name, axes_names=axes_names, axes_order=axes_order) out_frames.append(cel_frame) out_frame = (out_frames[0] if len(out_frames) == 1 else cf.CompositeFrame( out_frames, name='world')) return gWCS([(in_frame, transform), (out_frame, None)])
def test_round_trip_gwcs(tmpdir): """ Add a 2-step gWCS instance to NDAstroData, save to disk, reload & compare. """ from gwcs import coordinate_frames as cf from gwcs import WCS arr = np.zeros((10, 10), dtype=np.float32) ad1 = astrodata.create(fits.PrimaryHDU(), [fits.ImageHDU(arr, name='SCI')]) # Transformation from detector pixels to pixels in some reference row, # removing relative distortions in wavelength: det_frame = cf.Frame2D(name='det_mosaic', axes_names=('x', 'y'), unit=(u.pix, u.pix)) dref_frame = cf.Frame2D(name='dist_ref_row', axes_names=('xref', 'y'), unit=(u.pix, u.pix)) # A made-up example model that looks vaguely like some real distortions: fdist = models.Chebyshev2D(2, 2, c0_0=4.81125, c1_0=5.43375, c0_1=-0.135, c1_1=-0.405, c0_2=0.30375, c1_2=0.91125, x_domain=[0., 9.], y_domain=[0., 9.]) # This is not an accurate inverse, but will do for this test: idist = models.Chebyshev2D(2, 2, c0_0=4.89062675, c1_0=5.68581232, c2_0=-0.00590263, c0_1=0.11755526, c1_1=0.35652358, c2_1=-0.01193828, c0_2=-0.29996306, c1_2=-0.91823397, c2_2=0.02390594, x_domain=[-1.5, 12.], y_domain=[0., 9.]) # The resulting 2D co-ordinate mapping from detector to ref row pixels: distrans = models.Mapping((0, 1, 1)) | (fdist & models.Identity(1)) distrans.inverse = models.Mapping((0, 1, 1)) | (idist & models.Identity(1)) # Transformation from reference row pixels to linear, row-stacked spectra: spec_frame = cf.SpectralFrame(axes_order=(0, ), unit=u.nm, axes_names='lambda', name='wavelength') row_frame = cf.CoordinateFrame(1, 'SPATIAL', axes_order=(1, ), unit=u.pix, axes_names='y', name='row') rss_frame = cf.CompositeFrame([spec_frame, row_frame]) # Toy wavelength model & approximate inverse: fwcal = models.Chebyshev1D(2, c0=500.075, c1=0.05, c2=0.001, domain=[0, 9]) iwcal = models.Chebyshev1D(2, c0=4.59006292, c1=4.49601817, c2=-0.08989608, domain=[500.026, 500.126]) # The resulting 2D co-ordinate mapping from ref pixels to wavelength: wavtrans = fwcal & models.Identity(1) wavtrans.inverse = iwcal & models.Identity(1) # The complete WCS chain for these 2 transformation steps: ad1[0].nddata.wcs = WCS([(det_frame, distrans), (dref_frame, wavtrans), (rss_frame, None)]) # Save & re-load the AstroData instance with its new WCS attribute: testfile = str(tmpdir.join('round_trip_gwcs.fits')) ad1.write(testfile) ad2 = astrodata.open(testfile) wcs1 = ad1[0].nddata.wcs wcs2 = ad2[0].nddata.wcs # # Temporary workaround for issue #9809, to ensure the test is correct: # wcs2.forward_transform[1].x_domain = (0, 9) # wcs2.forward_transform[1].y_domain = (0, 9) # wcs2.forward_transform[3].domain = (0, 9) # wcs2.backward_transform[0].domain = (500.026, 500.126) # wcs2.backward_transform[3].x_domain = (-1.5, 12.) # wcs2.backward_transform[3].y_domain = (0, 9) # Did we actually get a gWCS instance back? assert isinstance(wcs2, WCS) # Do the transforms have the same number of submodels, with the same types, # degrees, domains & parameters? Here the inverse gets checked redundantly # as both backward_transform and forward_transform.inverse, but it would be # convoluted to ensure that both are correct otherwise (since the transforms # get regenerated as new compound models each time they are accessed). compare_models(wcs1.forward_transform, wcs2.forward_transform) compare_models(wcs1.backward_transform, wcs2.backward_transform) # Do the instances have matching co-ordinate frames? for f in wcs1.available_frames: assert repr(getattr(wcs1, f)) == repr(getattr(wcs2, f)) # Also compare a few transformed values, as the "proof of the pudding": y, x = np.mgrid[0:9:2, 0:9:2] np.testing.assert_allclose(wcs1(x, y), wcs2(x, y), rtol=1e-7, atol=0.) y, w = np.mgrid[0:9:2, 500.025:500.12:0.0225] np.testing.assert_allclose(wcs1.invert(w, y), wcs2.invert(w, y), rtol=1e-7, atol=0.)
def create_spectral_wcs(ra, dec, wavelength): """Assign a WCS for sky coordinates and a table of wavelengths Parameters ---------- ra: float The right ascension (in degrees) at the nominal location of the entrance aperture (slit). dec: float The declination (in degrees) at the nominal location of the entrance aperture. wavelength: ndarray The wavelength in microns at each pixel of the extracted spectrum. Returns: -------- wcs: a gwcs.wcs.WCS object This takes a float or sequence of float and returns a tuple of the right ascension, declination, and wavelength (or sequence of wavelengths) at the pixel(s) specified by the input argument. """ input_frame = cf.CoordinateFrame(naxes=1, axes_type=("SPATIAL", ), axes_order=(0, ), unit=(u.pix, ), name="pixel_frame") sky = cf.CelestialFrame(name='sky', axes_order=(0, 1), reference_frame=coord.ICRS()) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('wavelength', )) world = cf.CompositeFrame([sky, spec], name='world') pixel = np.arange(len(wavelength), dtype=np.float) tab = Mapping((0, 0, 0)) | \ Const1D(ra) & Const1D(dec) & Tabular1D(points=pixel, lookup_table=wavelength, bounds_error=False, fill_value=None) tab.name = "pixel_to_world" if all(np.diff(wavelength) > 0): tab.inverse = Mapping((2, )) | Tabular1D( points=wavelength, lookup_table=pixel, bounds_error=False, ) elif all(np.diff(wavelength) < 0): tab.inverse = Mapping((2, )) | Tabular1D( points=wavelength[::-1], lookup_table=pixel[::-1], bounds_error=False, ) else: log.warning( "Wavelengths are not strictly monotonic, inverse transform is not set" ) pipeline = [(input_frame, tab), (world, None)] return WCS(pipeline)
def __init__(self, spectrum=None, spectral_axis=None, wcs=None, **kwargs): # This handles cases where arithmetic is being performed, and an # object is created that's just a number if not isinstance(spectrum, (AstroData, NDData)): super().__init__(spectrum, spectral_axis=spectral_axis, wcs=wcs, **kwargs) return if isinstance(spectrum, AstroData) and not spectrum.is_single: raise TypeError("Input spectrum must be a single AstroData slice") # Unit handling try: # for NDData-like flux_unit = spectrum.unit except AttributeError: try: # for AstroData flux_unit = u.Unit(spectrum.hdr.get('BUNIT')) except (TypeError, ValueError): # unknown/missing flux_unit = None if flux_unit is None: flux_unit = u.dimensionless_unscaled try: kwargs['mask'] = spectrum.mask except AttributeError: flux = spectrum else: flux = spectrum.data kwargs['uncertainty'] = spectrum.uncertainty # If spectrum was a Quantity, it already has units so we'd better # not multiply them in again! if not isinstance(flux, u.Quantity): flux *= flux_unit # If no wavelength information is included, get it from the input if spectral_axis is None and wcs is None: if isinstance(spectrum, AstroData): if spectrum.wcs is not None: wcs = spectrum.wcs else: spec_unit = u.Unit(spectrum.hdr.get('CUNIT1', 'nm')) try: wavecal = dict( zip(spectrum.WAVECAL["name"], spectrum.WAVECAL["coefficients"])) except (AttributeError, KeyError): # make a Model from the FITS WCS info det2wave = (models.Shift(1 - spectrum.hdr['CRPIX1']) | models.Scale(spectrum.hdr['CD1_1']) | models.Shift(spectrum.hdr['CRVAL1'])) else: det2wave = am.dict_to_chebyshev(wavecal) det2wave.inverse = am.make_inverse_chebyshev1d( det2wave, sampling=1) spec_unit = u.nm detector_frame = cf.CoordinateFrame(1, axes_type='SPATIAL', axes_order=(0, ), unit=u.pix, axes_names='x') spec_frame = cf.SpectralFrame(unit=spec_unit, name='lambda') wcs = gWCS.WCS([(detector_frame, det2wave), (spec_frame, None)]) else: wcs = spectrum.wcs # from an NDData-like object super().__init__(flux=flux, spectral_axis=spectral_axis, wcs=wcs, **kwargs) self.filename = getattr(spectrum, 'filename', None)
def main(): path = Path('~/sunpy/data/jsocflare/').expanduser() files = glob.glob(str(path / '*.fits')) # requestid = 'JSOC_20180831_1097' requestid = None if not files: if requestid: c = JSOCClient() filesd = c.get_request(requestid, path=str(path), overwrite=False).wait() files = [] for f in filesd.values(): files.append(f['path']) else: results = Fido.search( a.jsoc.Time('2017-09-06T12:00:00', '2017-09-06T12:02:00'), a.jsoc.Series('aia.lev1_euv_12s'), a.jsoc.Segment('image'), a.jsoc.Notify("*****@*****.**")) print(results) files = Fido.fetch(results, path=str(path)) files.sort() files = np.array(files) # For each image get: # the index inds = [] # the time times = [] # the dt from the first image seconds = [] # the wavelength waves = [] for i, filepath in enumerate(files): with fits.open(filepath) as hdul: header = hdul[1].header time = parse_time(header['DATE-OBS']) if i == 0: root_header = header start_time = time inds.append(i) times.append(time) seconds.append((time - start_time).total_seconds()) waves.append(header['WAVELNTH']) # Construct an array and sort it by wavelength and time arr = np.array((inds, seconds, waves)).T sorter = np.lexsort((arr[:, 1], arr[:, 2])) # Using this double-sorted array get the list indicies list_sorter = np.array(arr[sorter][:, 0], dtype=int) # Calculate the desired shape of the output array n_waves = len(list(set(waves))) shape = (n_waves, len(files) // n_waves) # Construct a 2D array of filenames cube = files[list_sorter].reshape(shape) # Extract a list of coordinates in time and wavelength # this assumes all wavelength images are taken at the same time time_coords = np.array([t.isoformat() for t in times])[list_sorter].reshape(shape)[0, :] wave_coords = np.array(waves)[list_sorter].reshape(shape)[:, 0] smap0 = sunpy.map.Map(files[0]) spatial = map_to_transform(smap0) timemodel = generate_lookup_table(lookup_table=seconds[:shape[1]] * u.s) wavemodel = generate_lookup_table(lookup_table=waves[:shape[0]] * u.AA) hcubemodel = spatial & timemodel & wavemodel wave_frame = cf.SpectralFrame(axes_order=(3, ), unit=u.AA, name="wavelength", axes_names=("wavelength", )) time_frame = cf.TemporalFrame(axes_order=(2, ), unit=u.s, reference_time=Time(time_coords[0]), name="time", axes_names=("time", )) sky_frame = cf.CelestialFrame(axes_order=(0, 1), name='helioprojective', reference_frame=smap0.coordinate_frame, axes_names=("helioprojective longitude", "helioprojective latitude")) sky_frame = cf.CompositeFrame([sky_frame, time_frame, wave_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "time", "wavelength"), unit=(u.pix, u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=hcubemodel, input_frame=detector_frame, output_frame=sky_frame) print(repr(wcs)) print(wcs(*[1 * u.pix] * 4, with_units=True)) ea = references_from_filenames(cube, relative_to=str(path)) tree = { 'gwcs': wcs, 'dataset': ea, } with asdf.AsdfFile(tree) as ff: # ff.write_to("test.asdf") filename = str(path / "aia_{}.asdf".format(time_coords[0])) ff.write_to(filename) print("Saved to : {}".format(filename)) # import sys; sys.exit(0) from dkist.dataset import Dataset ds = Dataset.from_directory(str(path)) print(repr(ds)) print(repr(ds.wcs)) print(ds.wcs(*[1 * u.pix] * 4, with_units=True))
def main(): path = Path('~/Git/DKIST/dkist/dkist/data/test/EIT').expanduser() files = glob.glob(str(path / '*.fits')) files.sort() files = np.array(files) # For each image get: # the index inds = [] # the time times = [] # the dt from the first image seconds = [] headers = [] for i, filepath in enumerate(files): with fits.open(filepath) as hdul: header = hdul[0].header headers.append(dict(header)) time = parse_time(header['DATE-OBS']) if i == 0: start_time = time inds.append(i) times.append(time) seconds.append((time - start_time).to(u.s)) # Extract a list of coordinates in time and wavelength # this assumes all wavelength images are taken at the same time time_coords = np.array([str(t.isot) for t in times]) smap0 = sunpy.map.Map(files[0]) spatial = map_to_transform(smap0) timemodel = LookupTable(lookup_table=seconds * u.s) hcubemodel = spatial & timemodel sky_frame = cf.CelestialFrame(axes_order=(0, 1), name='helioprojective', reference_frame=smap0.coordinate_frame, axes_names=("helioprojective longitude", "helioprojective latitude")) time_frame = cf.TemporalFrame(axes_order=(2, ), unit=u.s, reference_time=Time(time_coords[0]), axes_names=("time", )) sky_frame = cf.CompositeFrame([sky_frame, time_frame], name="world") detector_frame = cf.CoordinateFrame(name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=hcubemodel, input_frame=detector_frame, output_frame=sky_frame) print(repr(wcs)) print(wcs(*[1 * u.pix] * 4, with_units=True)) ea = references_from_filenames(files, relative_to=str(path)) tree = { 'wcs': wcs, 'data': ea, 'headers': table_from_headers(headers), 'meta': generate_datset_inventory_from_headers(headers, "eit_test_dataset.asdf") } with asdf.AsdfFile(tree) as ff: filename = path / "eit_test_dataset.asdf" ff.write_to(filename) print("Saved to : {}".format(filename))