def diffrot_map(smap, time=None, dt=None, pad=False, **diffrot_kwargs):
    """
    Function to apply solar differential rotation to a sunpy map.

    Parameters
    ----------
    smap : `~sunpy.map`
        Original map that we want to transform.
    time : sunpy-compatible time
        date/time at which the input co-ordinate will be rotated to.
    dt : `~astropy.units.Quantity` or `datetime`
        Desired interval between the input map and returned map.
    pad : `bool`
        Whether to create a padded map for submaps to don't loose data

    Returns
    -------
    diffrot_map : `~sunpy.map`
        A map with the result of applying solar differential rotation to the
        input map.
    """
    if (time is not None) and (dt is not None):
        raise ValueError('Only a time or an interval is accepted')
    elif not (time or dt):
        raise ValueError(
            'Either a time or an interval (`dt=`) needs to be provided')
    elif time:
        new_time = parse_time(time)
        dt = (new_time - smap.date).total_seconds() * u.s
    else:
        new_time = smap.date + datetime.timedelta(seconds=dt.to(u.s).value)

    # Check for masked maps
    if smap.mask is not None:
        smap_data = np.ma.array(smap.data, mask=smap.mask)
    else:
        smap_data = smap.data

    submap = False
    # Check whether the input is a submap
    if ((2 * smap.rsun_obs >
         smap.top_right_coord.Tx - smap.bottom_left_coord.Tx)
            or (2 * smap.rsun_obs >
                smap.top_right_coord.Ty - smap.bottom_left_coord.Ty)):

        submap = True
        if pad:
            # Calculating the largest distance between the corners and their rotation values
            deltax = deltay = 0
            for corner in product(*product([0 * u.pix], smap.dimensions)):
                corner_world = smap.pixel_to_world(*corner)
                corner_world_rotated = solar_rotate_coordinate(
                    corner_world, new_time, **diffrot_kwargs)
                corner_px_rotated = smap.world_to_pixel(corner_world_rotated)
                dx = np.abs(corner_px_rotated.x - corner[0])
                dy = np.abs(corner_px_rotated.y - corner[1])
                deltax = dx if dx > deltax else deltax
                deltay = dy if dy > deltay else deltay

            deltax = np.int(np.ceil(deltax.value))
            deltay = np.int(np.ceil(deltay.value))
            # Create a new `smap` with the padding around it
            smap_data = np.pad(smap.data, ((deltay, deltay), (deltax, deltax)),
                               'constant',
                               constant_values=0)
            smap_meta = deepcopy(smap.meta)
            smap_meta['naxis2'], smap_meta['naxis1'] = smap_data.shape
            smap_meta['crpix1'] += deltax
            smap_meta['crpix2'] += deltay
            smap = sunpy.map.Map(smap_data, smap_meta)

    warp_args = {'smap': smap, 'dt': dt}
    warp_args.update(diffrot_kwargs)
    # Apply solar differential rotation as a scikit-image warp
    out = transform.warp(to_norm(smap_data),
                         inverse_map=_warp_sun_coordinates,
                         map_args=warp_args)

    # Recover the original intensity range.
    out = un_norm(out, smap.data)

    # Update the meta information with the new date and time, and reference pixel.
    out_meta = deepcopy(smap.meta)
    if out_meta.get('date_obs', False):
        del out_meta['date_obs']
    out_meta['date-obs'] = "{:%Y-%m-%dT%H:%M:%S}".format(new_time)

    if submap:
        crval_rotated = solar_rotate_coordinate(smap.reference_coordinate,
                                                new_time, **diffrot_kwargs)
        out_meta['crval1'] = crval_rotated.Tx.value
        out_meta['crval2'] = crval_rotated.Ty.value

    return sunpy.map.Map((out, out_meta))
Esempio n. 2
0
def test_to_norm():
    array_simple = np.array([10., 20., 30., 100.])
    assert_allclose(to_norm(array_simple), np.array([0.1, 0.2, 0.3, 1.]))
    array_simple_neg = np.array([-10., 0., 10., 90.])
    assert_allclose(to_norm(array_simple_neg), np.array([0, 0.1, 0.2, 1.]))
Esempio n. 3
0
def test_to_norm():
    array_simple = np.array([10., 20., 30., 100.])
    assert_allclose(to_norm(array_simple), np.array([0.1, 0.2, 0.3, 1.]))
    array_simple_neg = np.array([-10., 0., 10., 90.])
    assert_allclose(to_norm(array_simple_neg), np.array([0, 0.1, 0.2, 1.]))
def diffrot_map(smap, time=None, dt: u.s=None, pad=False, **diffrot_kwargs):
    """
    Function to apply solar differential rotation to a sunpy map.

    Parameters
    ----------
    smap : `~sunpy.map`
        Original map that we want to transform.
    time : sunpy-compatible time
        date/time at which the input co-ordinate will be rotated to.
    dt : `~astropy.units.Quantity` or `astropy.time.Time`
        Desired interval between the input map and returned map.
    pad : `bool`
        Whether to create a padded map for submaps to don't loose data

    Returns
    -------
    diffrot_map : `~sunpy.map`
        A map with the result of applying solar differential rotation to the
        input map.
    """
    # Only this function needs scikit image
    from skimage import transform
    from sunpy.image.util import to_norm, un_norm
    # Import map here for performance reasons.
    import sunpy.map

    if (time is not None) and (dt is not None):
        raise ValueError('Only a time or an interval is accepted')
    elif not (time or dt):
        raise ValueError('Either a time or an interval (`dt=`) needs to be provided')
    elif time:
        new_time = parse_time(time)
        dt = (new_time - smap.date).to(u.s)
    else:
        new_time = smap.date + dt

    # Check for masked maps
    if smap.mask is not None:
        smap_data = np.ma.array(smap.data, mask=smap.mask)
    else:
        smap_data = smap.data

    submap = False
    # Check whether the input is a submap
    if ((2 * smap.rsun_obs > smap.top_right_coord.Tx - smap.bottom_left_coord.Tx) or
        (2 * smap.rsun_obs > smap.top_right_coord.Ty - smap.bottom_left_coord.Ty)):

        submap = True
        if pad:
            # Calculating the largest distance between the corners and their rotation values
            deltax = deltay = 0
            for corner in product(*product([0 * u.pix], smap.dimensions)):
                corner_world = smap.pixel_to_world(*corner)
                corner_world_rotated = solar_rotate_coordinate(corner_world, new_time, **diffrot_kwargs)
                corner_px_rotated = smap.world_to_pixel(corner_world_rotated)
                dx = np.abs(corner_px_rotated.x - corner[0])
                dy = np.abs(corner_px_rotated.y - corner[1])
                deltax = dx if dx > deltax else deltax
                deltay = dy if dy > deltay else deltay

            deltax = np.int(np.ceil(deltax.value))
            deltay = np.int(np.ceil(deltay.value))
            # Create a new `smap` with the padding around it
            smap_data = np.pad(smap.data, ((deltay, deltay), (deltax, deltax)),
                               'constant', constant_values=0)
            smap_meta = deepcopy(smap.meta)
            smap_meta['naxis2'], smap_meta['naxis1'] = smap_data.shape
            smap_meta['crpix1'] += deltax
            smap_meta['crpix2'] += deltay
            smap = sunpy.map.Map(smap_data, smap_meta)

    warp_args = {'smap': smap, 'dt': dt}
    warp_args.update(diffrot_kwargs)
    # Apply solar differential rotation as a scikit-image warp
    out = transform.warp(to_norm(smap_data), inverse_map=_warp_sun_coordinates,
                         map_args=warp_args)

    # Recover the original intensity range.
    out = un_norm(out, smap.data)

    # Update the meta information with the new date and time, and reference pixel.
    out_meta = deepcopy(smap.meta)
    if out_meta.get('date_obs', False):
        del out_meta['date_obs']
    out_meta['date-obs'] = "{}".format(new_time.strftime('%Y-%m-%dT%H:%M:%S'))

    if submap:
        crval_rotated = solar_rotate_coordinate(smap.reference_coordinate, new_time, **diffrot_kwargs)
        out_meta['crval1'] = crval_rotated.Tx.value
        out_meta['crval2'] = crval_rotated.Ty.value

    return sunpy.map.Map((out, out_meta))
Esempio n. 5
0
def differential_rotate(smap, observer=None, time=None, **diff_rot_kwargs):
    """
    Function to transform a `~sunpy.map.Map` taking into account simultaneously
    both solar differential rotation and the changing location of the
    observer.

    The function transforms the input map data pixels by first rotating each
    pixel according to solar differential rotation.  The amount of solar
    differential applied is calculated by the time difference between the
    observation time of map and the new observation time, as specified by either the
    "time" keyword or the "obstime" property of the "observer" keyword.
    The location of the rotated pixels are then transformed to locations on the Sun
    as seen from the new observer position.  This is desirable since in most cases
    the observer does not remain at a fixed position in space. If
    the "time" keyword is used then the new observer position is assumed to
    be based on the location of the Earth.  If the "observer" keyword is used then
    this defines the new observer position.

    The function works with full disk maps and maps that contain portions of the
    solar disk (maps that are entirely off-disk will raise an error).  When the
    input map contains the full disk, the output map has the same dimensions as
    the input map.  When the input map images only part of the solar disk, only
    the on-disk pixels are differentially rotated and the output map can have
    a different dimensions compared to the input map.  In this case any off-disk
    emission shown in the input map is not included in the output map.

    Parameters
    ----------
    smap : `~sunpy.map.GenericMap`
        Original map that we want to transform.
    observer : `~astropy.coordinates.BaseCoordinateFrame`, `~astropy.coordinates.SkyCoord`, `None`, optional
        The location of the new observer.
        Instruments in Earth orbit can be approximated by using the position
        of the Earth at the observation time of the new observer.
    time : sunpy-compatible time, `~astropy.time.TimeDelta`, `~astropy.units.Quantity`, `None`, optional
        Used to define the duration over which the amount of solar rotation is
        calculated.  If 'time' is an `~astropy.time.Time` then the time interval
        is difference between 'time' and the map observation time. If 'time' is
        `~astropy.time.TimeDelta` or `~astropy.units.Quantity` then the calculation
        is "initial_obstime + time".

    Returns
    -------
    `~sunpy.map.GenericMap`
        A map with the result of applying solar differential rotation to the
        input map.
    """
    # If the entire map is off-disk, return an error so the user is aware.
    if is_all_off_disk(smap):
        raise ValueError(
            "The entire map is off disk. No data to differentially rotate.")

    # Get the new observer
    new_observer = _get_new_observer(smap.date, observer, time)

    # Only this function needs scikit image
    from skimage import transform
    from sunpy.image.util import to_norm, un_norm

    # Check whether the input contains the full disk of the Sun
    is_sub_full_disk = not contains_full_disk(smap)
    if is_sub_full_disk:
        # Find the minimal submap of the input map that includes all the
        # on disk pixels. This is required in order to calculate how
        # much to pad the output (solar-differentially rotated) data array by
        # compared to the input map.
        # The amount of padding is dependent on the amount of solar differential
        # rotation and where the on-disk pixels are (since these pixels are the only ones
        # subject to solar differential rotation).
        if not is_all_on_disk(smap):
            # Get the bottom left and top right coordinates that are the
            # vertices that define a box that encloses the on disk pixels
            bottom_left, top_right = on_disk_bounding_coordinates(smap)

            # Create a submap that excludes the off disk emission that does
            # not need to be rotated.
            smap = smap.submap(bottom_left, top_right)
        bottom_left = smap.bottom_left_coord
        top_right = smap.top_right_coord

        # Get the edges of the minimal submap that contains all the on-disk pixels.
        edges = map_edges(smap)

        # Calculate where the output array moves to.
        # Rotate the top and bottom edges
        rotated_top = _rotate_submap_edge(smap,
                                          edges[0],
                                          observer=new_observer,
                                          **diff_rot_kwargs)
        rotated_bottom = _rotate_submap_edge(smap,
                                             edges[1],
                                             observer=new_observer,
                                             **diff_rot_kwargs)

        # Rotate the left and right hand edges
        rotated_lhs = _rotate_submap_edge(smap,
                                          edges[2],
                                          observer=new_observer,
                                          **diff_rot_kwargs)
        rotated_rhs = _rotate_submap_edge(smap,
                                          edges[3],
                                          observer=new_observer,
                                          **diff_rot_kwargs)

        # Calculate the bounding box of the rotated map
        rotated_bl, rotated_tr = _get_bounding_coordinates(
            [rotated_top, rotated_bottom, rotated_lhs, rotated_rhs])

        # Calculate the maximum distance in pixels the map has moved by comparing
        # how far the original and rotated bounding boxes have moved.
        diff_x = [(np.abs(rotated_bl.Tx - bottom_left.Tx)).value,
                  (np.abs(rotated_tr.Tx - top_right.Tx)).value]
        deltax = int(np.ceil(np.max(diff_x) / smap.scale.axis1).value)

        diff_y = [(np.abs(rotated_bl.Ty - bottom_left.Ty)).value,
                  (np.abs(rotated_tr.Ty - top_right.Ty)).value]
        deltay = int(np.ceil(np.max(diff_y) / smap.scale.axis2).value)

        # Create a new `smap` with the padding around it
        padded_data = np.pad(smap.data, ((deltay, deltay), (deltax, deltax)),
                             'constant',
                             constant_values=0)
        padded_meta = deepcopy(smap.meta)
        padded_meta['naxis2'], padded_meta['naxis1'] = smap.data.shape

        padded_meta['crpix1'] += deltax
        padded_meta['crpix2'] += deltay

        # Create the padded map that will be used to create the rotated map.
        smap = smap._new_instance(padded_data, padded_meta)

    # Check for masked maps
    if smap.mask is not None:
        smap_data = np.ma.array(smap.data, mask=smap.mask)
    else:
        smap_data = smap.data

    # Create the arguments for the warp function.
    warp_args = {'smap': smap, 'new_observer': new_observer}
    warp_args.update(diff_rot_kwargs)

    # Apply solar differential rotation as a scikit-image warp
    out_data = transform.warp(to_norm(smap_data),
                              inverse_map=_warp_sun_coordinates,
                              map_args=warp_args)

    # Recover the original intensity range.
    out_data = un_norm(out_data, smap.data)

    # Update the meta information with the new date and time.
    out_meta = deepcopy(smap.meta)
    if out_meta.get('date_obs', False):
        del out_meta['date_obs']
    out_meta['date-obs'] = new_observer.obstime.strftime(
        "%Y-%m-%dT%H:%M:%S.%f")

    # Need to update the observer location for the output map.
    # Remove all the possible observer keys
    all_keys = expand_list(
        [e[0] for e in smap._supported_observer_coordinates])
    for key in all_keys:
        out_meta.pop(key)

    # Add a new HGS observer
    out_meta['hglt_obs'] = new_observer.lat.value
    out_meta['hgln_obs'] = new_observer.lon.value
    out_meta['dsun_obs'] = new_observer.radius.to(u.m).value

    if is_sub_full_disk:
        # Define a new reference pixel and the value at the reference pixel.
        # Note that according to the FITS convention the first pixel in the
        # image is at (1.0, 1.0).
        center_rotated = solar_rotate_coordinate(smap.center,
                                                 observer=new_observer,
                                                 **diff_rot_kwargs)
        out_meta['crval1'] = center_rotated.Tx.value
        out_meta['crval2'] = center_rotated.Ty.value
        out_meta['crpix1'] = 1 + smap.data.shape[1] / 2.0 + (
            (center_rotated.Tx - smap.center.Tx) / smap.scale.axis1).value
        out_meta['crpix2'] = 1 + smap.data.shape[0] / 2.0 + (
            (center_rotated.Ty - smap.center.Ty) / smap.scale.axis2).value
        return smap._new_instance(out_data,
                                  out_meta).submap(rotated_bl, rotated_tr)
    else:
        return smap._new_instance(out_data, out_meta)