def __init__(self, x, y, copy=True, **kwargs): if x is None or y is None: raise ValueError( 'x and y are required to instantiate CartesianRepresentation') if not isinstance(x, self.attr_classes['x']): raise TypeError('x should be a {0}'.format( self.attr_classes['x'].__name__)) if not isinstance(y, self.attr_classes['y']): raise TypeError('y should be a {0}'.format( self.attr_classes['y'].__name__)) x = self.attr_classes['x'](x, copy=copy) y = self.attr_classes['y'](y, copy=copy) if not (x.unit.physical_type == y.unit.physical_type): raise u.UnitsError("x and y should have matching physical types") try: x, y = np.broadcast_arrays(x, y, subok=True) except ValueError: raise ValueError("Input parameters x and y cannot be broadcast") self._x = x self._y = y self._differentials = {}
def radial_velocity(self, val): if val is not None: if not val.unit.is_equivalent(u.km / u.s): raise u.UnitsError("Radial velocity must be a velocity.") new_spectral_axis = self.spectral_axis.with_radial_velocity(val) self._spectral_axis = new_spectral_axis
def rest_value(self, value): if not hasattr(value, 'unit') or not value.unit.is_equivalent( u.Hz, u.spectral()): raise u.UnitsError( "Rest value must be energy/wavelength/frequency equivalent.") self._rest_value = value
def _parse_dimension(dim): """ Parses the radius and returns it in the format expected by UKIDSS. Parameters ---------- dim : str, `~astropy.units.Quantity` Returns ------- dim_in_min : float The value of the radius in arcminutes. """ if isinstance(dim, u.Quantity) and dim.unit in u.deg.find_equivalent_units(): dim_in_min = dim.to(u.arcmin).value # otherwise must be an Angle or be specified in hours... else: try: new_dim = commons.parse_radius(dim).degree dim_in_min = u.Quantity(value=new_dim, unit=u.deg).to(u.arcmin).value except (u.UnitsError, coord.errors.UnitsError, AttributeError): raise u.UnitsError("Dimension not in proper units") return dim_in_min
def __init__(self, flux, spectral_axis=None, wcs=None, uncertainty=None, mask=None, meta=None): # Check for quantity if not isinstance(flux, u.Quantity): raise u.UnitsError("Flux must be a `Quantity`.") if spectral_axis is not None: if not isinstance(spectral_axis, u.Quantity): raise u.UnitsError("Spectral axis must be a `Quantity`.") # Ensure that the input values are the same shape if not (flux.shape == spectral_axis.shape): raise ValueError( "Shape of all data elements must be the same.") if uncertainty is not None and uncertainty.array.shape != flux.shape: raise ValueError("Uncertainty must be the same shape as flux and " "spectral axis.") if mask is not None and mask.shape != flux.shape: raise ValueError("Mask must be the same shape as flux and " "spectral axis.") # Convert uncertainties to standard deviations if not already defined # to be of some type if uncertainty is not None and not isinstance(uncertainty, NDUncertainty): # If the uncertainties are not provided a unit, raise a warning # and use the flux units if not isinstance(uncertainty, u.Quantity): logging.warning("No unit associated with uncertainty, assuming" "flux units of '{}'.".format(flux.unit)) uncertainty = u.Quantity(uncertainty, unit=flux.unit) uncertainty = StdDevUncertainty(uncertainty) self._flux = flux self._spectral_axis = spectral_axis self._wcs = wcs self._uncertainty = uncertainty self._mask = mask self._meta = meta
def from_geocentric(cls, x, y, z, unit=None): """ Location on Earth, initialized from geocentric coordinates. Parameters ---------- x, y, z : `~astropy.units.Quantity` or array-like Cartesian coordinates. If not quantities, ``unit`` should be given. unit : `~astropy.units.UnitBase` object or None Physical unit of the coordinate values. If ``x``, ``y``, and/or ``z`` are quantities, they will be converted to this unit. Raises ------ astropy.units.UnitsError If the units on ``x``, ``y``, and ``z`` do not match or an invalid unit is given. ValueError If the shapes of ``x``, ``y``, and ``z`` do not match. TypeError If ``x`` is not a `~astropy.units.Quantity` and no unit is given. """ if unit is None: try: unit = x.unit except AttributeError: raise TypeError("Geocentric coordinates should be Quantities " "unless an explicit unit is given.") else: unit = u.Unit(unit) if unit.physical_type != 'length': raise u.UnitsError("Geocentric coordinates should be in " "units of length.") try: x = u.Quantity(x, unit, copy=False) y = u.Quantity(y, unit, copy=False) z = u.Quantity(z, unit, copy=False) except u.UnitsError: raise u.UnitsError("Geocentric coordinate units should all be " "consistent.") x, y, z = np.broadcast_arrays(x, y, z) struc = np.empty(x.shape, cls._location_dtype) struc['x'], struc['y'], struc['z'] = x, y, z return super().__new__(cls, struc, unit, copy=False)
def _new_spectrum_with(self, data=None, wcs=None, mask=None, meta=None, fill_value=None, spectral_unit=None, unit=None, header=None, wcs_tolerance=None, **kwargs): data = self._data if data is None else data if unit is None and hasattr(data, 'unit'): if data.unit != self.unit: raise u.UnitsError("New data unit '{0}' does not" " match unit '{1}'. You can" " override this by specifying the" " `unit` keyword." .format(data.unit, self.unit)) unit = data.unit elif unit is None: unit = self.unit elif unit is not None: # convert string units to Units if not isinstance(unit, u.Unit): unit = u.Unit(unit) if hasattr(data, 'unit'): if u.Unit(unit) != data.unit: raise u.UnitsError("The specified new cube unit '{0}' " "does not match the input unit '{1}'." .format(unit, data.unit)) else: data = u.Quantity(data, unit=unit, copy=False) wcs = self._wcs if wcs is None else wcs mask = self._mask if mask is None else mask if meta is None: meta = {} meta.update(self._meta) if unit is not None: meta['BUNIT'] = unit.to_string(format='FITS') fill_value = self._fill_value if fill_value is None else fill_value spectral_unit = self._spectral_unit if spectral_unit is None else u.Unit(spectral_unit) spectrum = self.__class__(value=data, wcs=wcs, mask=mask, meta=meta, unit=unit, fill_value=fill_value, header=header or self._header, wcs_tolerance=wcs_tolerance or self._wcs_tolerance, **kwargs) spectrum._spectral_unit = spectral_unit return spectrum
def hcrs_to_icrs(hcrs_coo, icrs_frame): # this is just an origin translation so without a distance it cannot go ahead if isinstance(hcrs_coo.data, UnitSphericalRepresentation): raise u.UnitsError( _NEED_ORIGIN_HINT.format(hcrs_coo.__class__.__name__)) return None, get_offset_sun_from_barycenter( hcrs_coo.obstime, include_velocity=bool(hcrs_coo.data.differentials))
def __new__(cls, dm, unit=None, **kwargs): if unit is None: unit = getattr(dm, 'unit', cls._default_unit) self = super(DispersionMeasure, cls).__new__(cls, dm, unit, **kwargs) if not self.unit.is_equivalent(cls._default_unit): raise u.UnitsError("Dispersion measures should have units " "equivalent to pc/cm^3") return self
def with_observer_stationary_relative_to(self, frame, velocity=None, preserve_observer_frame=False): if self.unit is u.pixel: raise u.UnitsError("Cannot transform spectral coordinates in pixel units") super().with_observer_stationary_relative_to(frame, velocity=velocity, preserve_observer_frame=preserve_observer_frame)
def with_radial_velocity_shift(self, target_shift=None, observer_shift=None): if self.unit is u.pixel: raise u.UnitsError( "Cannot transform spectral coordinates in pixel units") return super().with_radial_velocity_shift( target_shift=target_shift, observer_shift=observer_shift)
def units_filter(quantity, unit): """ Convert quantity to given units and extract value """ if not isinstance(quantity, u.Quantity): raise u.UnitsError( f'Value must be a quantity with units compatible with {unit}') return quantity.to(unit).value
def subtract_dark(self, image, dark, scale=False, exposure_time=None, exposure_unit=None): """ Subtract dark current from an image. Parameters ---------- image : `~astropy.nddata.CCDData` Image from which dark will be subtracted. dark : `~astropy.nddata.CCDData` Dark image. exposure_time : str or `~ccdproc.Keyword` or None, optional Name of key in image metadata that contains exposure time. Default is ``None``. exposure_unit : `~astropy.units.Unit` or None, optional Unit of the exposure time if the value in the meta data does not include a unit. Default is ``None``. scale: bool, optional If True, scale the dark frame by the exposure times. Default is ``False``. {log} Returns ------- result : `~astropy.nddata.CCDData` Dark-subtracted image. """ self.log.debug('Subtracting dark...') result = image.copy() try: # if dark current is linear, then this first step scales the provided # dark to match the exposure time if scale: dark_scaled = dark.copy() data_exposure = image.header[exposure_time] dark_exposure = dark.header[exposure_time] # data_exposure and dark_exposure are both quantities, # so we can just have subtract do the scaling dark_scaled = dark_scaled.multiply(data_exposure / dark_exposure) result.data = image.data - dark_scaled.data else: result.data = image.data - dark.data except (u.UnitsError, u.UnitConversionError, ValueError) as e: # Make the error message a little more explicit than what is returned # by default. raise u.UnitsError("Unit '{}' of the uncalibrated image does not " "match unit '{}' of the calibration " "image".format(image.unit, dark.unit)) self.log.debug('Subtracted dark.') return result
def __new__(cls, value, unit=None, observer=None, target=None, radial_velocity=None, redshift=None, **kwargs): obj = super().__new__(cls, value, unit=unit, **kwargs) # There are two main modes of operation in this class. Either the # observer and target are both defined, in which case the radial # velocity and redshift are automatically computed from these, or # only one of the observer and target are specified, along with a # manually specified radial velocity or redshift. So if a target and # observer are both specified, we can't also accept a radial velocity # or redshift. if target is not None and observer is not None: if radial_velocity is not None or redshift is not None: raise ValueError( "Cannot specify radial velocity or redshift if both " "target and observer are specified") # We only deal with redshifts here and in the redshift property. # Otherwise internally we always deal with velocities. if redshift is not None: if radial_velocity is not None: raise ValueError( "Cannot set both a radial velocity and redshift") redshift = u.Quantity(redshift) # For now, we can't specify redshift=u.one in quantity_input above # and have it work with plain floats, but if that is fixed, for # example as in https://github.com/astropy/astropy/pull/10232, we # can remove the check here and add redshift=u.one to the decorator if not redshift.unit.is_equivalent(u.one): raise u.UnitsError('redshift should be dimensionless') radial_velocity = _redshift_to_velocity(redshift) # If we're initializing from an existing SpectralCoord, keep any # parameters that aren't being overridden if observer is None: observer = getattr(value, 'observer', None) if target is None: target = getattr(value, 'target', None) # As mentioned above, we should only specify the radial velocity # manually if either or both the observer and target are not # specified. if observer is None or target is None: if radial_velocity is None: radial_velocity = getattr(value, 'radial_velocity', None) obj._radial_velocity = radial_velocity obj._observer = cls._validate_coordinate(observer, label='observer') obj._target = cls._validate_coordinate(target, label='target') return obj
def __new__(cls, dm, unit=None, dtype=None, copy=True): if unit is None: unit = getattr(dm, 'unit', cls._default_unit) self = super(DispersionMeasure, cls).__new__(cls, dm, unit, dtype=dtype, copy=copy) if not self.unit.is_equivalent(cls._default_unit): raise u.UnitsError("Dispersion measures should have units of " "pc/cm^3") return self
def _check_units(inputs): """Check for consistent units on data, error, and background.""" has_unit = [hasattr(x, 'unit') for x in inputs] if any(has_unit) and not all(has_unit): raise ValueError('If any of data, error, or background has units, ' 'then they all must all have units.') if all(has_unit): if any([inputs[0].unit != getattr(x, 'unit') for x in inputs[1:]]): raise u.UnitsError( 'data, error, and background units do not match.')
def ylim(self, range): if isinstance(range[0], u.Quantity) is isinstance(range[1], u.Quantity): if isinstance(range[0], u.Quantity): if not range[0].unit.is_equivalent(range[1].unit): raise u.UnitsError(f'The units of ymin ({range[0].unit}) are ' f'not compatible with the units of ymax ({range[1].unit})') else: raise ValueError('Either both or neither limit has to be specified ' 'as a Quantity') self._ylim = range
def __init__(self, wn, od, **kwargs): """ AbsorptionSpectrum(wn,od,**kwargs) Constructor for the `AbsorptionSpectrum` class. Parameters ---------- wn : `astropy.units.Quantity` The absorption spectrum frequency data. Unlike `BaseSpectrum`, the initialisation of `AbsorptionSpectrum` requires this to be in the specific units of reciprocal wavenumber. However, if it is in a quantity convertable to kayser, conversion will be attempted while a warning is given to notify the user of this. od : `astropy.units.Quantity` The absorption spectrum optical depth data. Unlike `BaseSpectrum`, the initialisation of `AbsorptionSpectrum` requires this to be in the specific units of optical depth units (from `omnifit.utils.unit_od`). **kwargs : Arguments, optional Additional initialisation arguments can be passed to `BaseSpectrum` using this. Note that x and y are defined using the other initialisation parameters of `AbsorptionSpectrum`. """ if type(wn) != u.quantity.Quantity: raise u.UnitsError('Input wn is not an astropy quantity.') if wn.unit != u.kayser: warnings.warn('Input wn is not in kayser units. Converting...', RuntimeWarning) with u.set_enabled_equivalencies(u.equivalencies.spectral()): wn = wn.to(u.kayser) if type(od) != u.quantity.Quantity: raise u.UnitsError('Input od is not an astropy quantity.') if od.unit != utils.unit_od: raise u.UnitsError('Input od is not in optical depth units.') if len(wn) != len(od): raise RuntimeError('Input arrays have different sizes.') self.wn = wn with u.set_enabled_equivalencies(u.equivalencies.spectral()): self.wl = self.wn.to(u.micron) self.od = od BaseSpectrum.__init__(self, self.wn, self.od, **kwargs)
def closest_spectral_channel(self, value, rest_frequency=None): """ Find the index of the closest spectral channel to the specified spectral coordinate. Parameters ---------- value : :class:`~astropy.units.Quantity` The value of the spectral coordinate to search for. rest_frequency : :class:`~astropy.units.Quantity` The rest frequency for any Doppler conversions """ # TODO: we have to not compute this every time spectral_axis = self.spectral_axis try: value = value.to(spectral_axis.unit, equivalencies=u.spectral()) except u.UnitsError: if value.unit.is_equivalent(spectral_axis.unit, equivalencies=u.doppler_radio(None)): if rest_frequency is None: raise u.UnitsError( "{0} cannot be converted to {1} without a " "rest frequency".format(value.unit, spectral_axis.unit)) else: try: value = value.to( spectral_axis.unit, equivalencies=u.doppler_radio(rest_frequency)) except u.UnitsError: raise u.UnitsError( "{0} cannot be converted to {1}".format( value.unit, spectral_axis.unit)) else: raise u.UnitsError( "'value' should be in frequency equivalent or velocity units (got {0})" .format(value.unit)) # TODO: optimize the next line - just brute force for now return np.argmin(np.abs(spectral_axis - value))
def in_time_interval(self, time: u.Quantity): """ Return `True` if the time is between `time_start` and `time_max`, and `False` otherwise. If `time` is not a valid time, then raise a `~astropy.units.UnitsError`. """ if not isinstance(time, u.Quantity): raise TypeError if not time.unit.physical_type == 'time': raise u.UnitsError(f"{time} is not a valid time.") return self.time_start <= time <= self.time_max
def _parameter_units_for_data_units(self, inputs_unit, outputs_unit): # Note that here we need to make sure that x and y are in the same # units otherwise this can lead to issues since rotation is not well # defined. if inputs_unit['x'] != inputs_unit['y']: raise u.UnitsError("Units of 'x' and 'y' inputs should match") return OrderedDict([('x_mean', inputs_unit['x']), ('y_mean', inputs_unit['x']), ('stddev', inputs_unit['x']), ('ratio', u.dimensionless_unscaled), ('amplitude', outputs_unit['z'])])
def distance(self, new_distance): if not isinstance(new_distance, u.Quantity): self._distance = new_distance * 1000. * u.pc else: if new_distance.unit.physical_type != 'distance': TypeError('Wrong type of new_distance!') if (new_distance.unit == "pc") or (new_distance.unit == "kpc"): self._distance = new_distance else: raise u.UnitsError( 'Allowed units for Lens distance are "pc" or "kpc"')
def distance(self, new_distance): if new_distance is None: self._distance = new_distance else: if not isinstance(new_distance, u.Quantity): self._distance = new_distance * 1000. * u.pc else: if (new_distance.unit == "pc") or (new_distance.unit == "kpc"): self._distance = new_distance else: raise u.UnitsError( 'Allowed units for Source distance are "pc" or "kpc"')
def _tuple_to_float(angle, unit): """ Converts an angle represented as a 3-tuple or 2-tuple into a floating point number in the given unit. """ # TODO: Numpy array of tuples? if unit == u.hourangle: return util.hms_to_hours(*angle) elif unit == u.degree: return util.dms_to_degrees(*angle) else: raise u.UnitsError(f"Can not parse '{angle}' as unit '{unit}'")
def _validate(self, value): if isinstance(value, u.Quantity) and value.unit != u.pixel: raise u.UnitsError(f'{self.name} must be in pixel units') if np.any(~np.isfinite(value)): raise ValueError(f'{self.name} must not contain any non-finite ' '(e.g., NaN or inf) positions') value = np.atleast_2d(value) if (value.shape[1] != 2 and value.shape[0] != 2) or value.ndim > 2: raise TypeError(f'{self.name} must be a (x, y) pixel position ' 'or a list or array of (x, y) pixel positions.')
def _compare_units(self, other, xy): """ Check units match another spectrum or kind of unit """ if isinstance(other, (str, u.UnitBase)): #check specific unit if xy == 'x': if self.x_unit != u.Unit(other): raise u.UnitsError("x_units differ") elif xy == 'y': if self.y_unit != u.Unit(other): raise u.UnitsError("y_units differ") else: raise ValueError("xy not 'x' or 'y'") elif isinstance(other, u.Quantity): _compare_units(self, other.unit, xy) elif isinstance(other, Spectrum): #compare two spectra if xy == 'x': if self.x_unit != other.x_unit: raise u.UnitsError("x_units differ") elif xy == 'y': if self.y_unit != other.y_unit: raise u.UnitsError("y_units differ") elif xy == 'xy': if self.x_unit != other.x_unit: raise u.UnitsError("x_units differ") if self.y_unit != other.y_unit: raise u.UnitsError("y_units differ") else: raise ValueError("xy not 'x', 'y', or 'xy'") else: raise TypeError( "other was not Spectrum or interpretable as a unit")
def _parse_dimension(dim): if (isinstance(dim, u.Quantity) and dim.unit in u.deg.find_equivalent_units()): if dim.unit not in ['arcsec', 'arcmin', 'deg']: dim = dim.to(u.degree) # otherwise must be an Angle or be specified in hours... else: try: new_dim = coord.Angle(dim) dim = u.Quantity(new_dim.degree, u.Unit('degree')) except (u.UnitsError, coord.errors.UnitsError, AttributeError): raise u.UnitsError("Dimension not in proper units") return dim
def subimage(self, xlo='min', xhi='max', ylo='min', yhi='max'): """ Extract a region spatially. Parameters ---------- [xy]lo/[xy]hi : int or `astropy.units.Quantity` or `min`/`max` The endpoints to extract. If given as a quantity, will be interpreted as World coordinates. If given as a string or int, will be interpreted as pixel coordinates. """ self._raise_wcs_no_celestial() limit_dict = { 'xlo': 0 if xlo == 'min' else xlo, 'ylo': 0 if ylo == 'min' else ylo, 'xhi': self.shape[1] if xhi == 'max' else xhi, 'yhi': self.shape[0] if yhi == 'max' else yhi } dims = {'x': 1, 'y': 0} for val in (xlo, ylo, xhi, yhi): if hasattr(val, 'unit') and not val.unit.is_equivalent(u.degree): raise u.UnitsError("The X and Y slices must be specified in " "degree-equivalent units.") for lim in limit_dict: limval = limit_dict[lim] if hasattr(limval, 'unit'): dim = dims[lim[0]] sl = [slice(0, 1)] sl.insert(dim, slice(None)) spine = self.world[sl][dim] val = np.argmin(np.abs(limval - spine)) if limval > spine.max() or limval < spine.min(): pass # log.warn("The limit {0} is out of bounds." # " Using min/max instead.".format(lim)) if lim[1:] == 'hi': # End-inclusive indexing: need to add one for the high # slice limit_dict[lim] = val + 1 else: limit_dict[lim] = val slices = [ slice(limit_dict[xx + 'lo'], limit_dict[xx + 'hi']) for xx in 'yx' ] return self[slices]
def match_and_remove_unit(var, var_to_match): ''' Make sure that `var` has the same unit as `var_to_match`, if any, and then remove the unit. If `var_to_match` has no unit, the value of `var` is returned, without its unit if it has one. Parameters ---------- var: any type the variable to be tested var_to_match: any type the variable whose unit must be matched Returns ------- value the value of `var` (without unit attached) after the unit has been changed to be the same as `var_to_match` Raises ------ u.UnitsError if `var` cannot be converted to `var_to_match` using the standard astropy method `var`.to() Examples -------- >>> a = 42*u.km >>> b = 42*u.m >>> match_and_remove_unit(a,b) 42000.0 >>> a = 42 >>> c = 3.1415 >>> match_and_remove_unit(a,c) 42 ''' if isinstance(var, u.Quantity) and isinstance(var_to_match, u.Quantity): # Catch the errors here to have nicer tracebacks, otherwise # we end up deep into astropy libraries. try: return var.to(var_to_match.unit).value except Exception as e: raise u.UnitsError(str(e)) elif isinstance(var, u.Quantity): return var.value else: return var
def prep_plot_kwargs(naxis, wcs, plot_axes, axes_coordinates, axes_units): """ Prepare the kwargs for the plotting functions. This function accepts things in array order and returns things in WCS order. """ # If plot_axes, axes_coordinates, axes_units are not None and not lists, # convert to lists for consistent indexing behaviour. if (not isinstance(plot_axes, (tuple, list))) and (plot_axes is not None): plot_axes = [plot_axes] if (not isinstance(axes_coordinates, (tuple, list))) and (axes_coordinates is not None): axes_coordinates = [axes_coordinates] if (not isinstance(axes_units, (tuple, list))) and (axes_units is not None): axes_units = [axes_units] # Set default value of plot_axes if not set by user. if plot_axes is None: plot_axes = [..., 'y', 'x'] # We flip the plot axes here so they are in the right order for WCSAxes plot_axes = plot_axes[::-1] plot_axes = _expand_ellipsis(naxis, plot_axes) if 'x' not in plot_axes: raise ValueError("'x' must be in plot_axes.") if axes_coordinates is not None: axes_coordinates = _expand_ellipsis_axis_coordinates(axes_coordinates, wcs.world_axis_physical_types) # Ensure all elements in axes_coordinates are of correct types. ax_coord_types = (str, type(None)) for axis_coordinate in axes_coordinates: if isinstance(axis_coordinate, str): # coordinates can be accessed by either name or type if axis_coordinate not in set(wcs.world_axis_physical_types).union(set(wcs.world_axis_names)): raise ValueError(f"{axis_coordinate} is not one of this cubes world axis physical types.") if not isinstance(axis_coordinate, ax_coord_types): raise TypeError(f"axes_coordinates must be one of {ax_coord_types} or list of those, not {type(axis_coordinate)}.") if axes_units is not None: axes_units = _expand_ellipsis(wcs.world_n_dim, axes_units) if len(axes_units) != wcs.world_n_dim: raise ValueError(f"The length of the axes_units argument must be {wcs.world_n_dim}.") # Convert all non-None elements to astropy units axes_units = list(map(lambda x: u.Unit(x) if x is not None else None, axes_units))[::-1] for i, axis_unit in enumerate(axes_units): wau = wcs.world_axis_units[i] if axis_unit is not None and not axis_unit.is_equivalent(wau): raise u.UnitsError( f"Specified axis unit '{axis_unit}' is not convertible to world axis unit '{wau}'") return plot_axes, axes_coordinates, axes_units