def compute_coastal_change(old_mosaic, new_mosaic, no_data = -9999):
    """Compute the coastal change and coastlines for two mosaics

    Computes the output products and appends them onto the old mosaic as
    coastal_change, coastline_old, coastline_new

    Args:
        old_mosaic, new_mosaic - single timeslice mosaic data.

    Returns:
        Xarray dataset containing all original data with three new variables.

    """
    # Create a combined bitmask - cfmask if it exists, otherwise pixel_qa.
    combined_mask = create_cfmask_clean_mask(old_mosaic.cf_mask) & create_cfmask_clean_mask(
        new_mosaic.cf_mask) if 'cf_mask' in old_mosaic else create_bit_mask(
            old_mosaic.pixel_qa, [1, 2]) & create_bit_mask(new_mosaic.pixel_qa, [1, 2])

    old_water = wofs_classify(old_mosaic, mosaic=True, clean_mask=combined_mask, no_data = no_data)
    new_water = wofs_classify(new_mosaic, mosaic=True, clean_mask=combined_mask, no_data = no_data)

    coastal_change = new_water - old_water

    coastal_change = coastal_change.where(coastal_change.wofs != 0)

    new_coastline = _coastline_classification_2(new_water)
    old_coastline = _coastline_classification_2(old_water)

    old_mosaic['coastal_change'] = coastal_change.wofs
    old_mosaic['coastline_old'] = old_coastline.coastline
    old_mosaic['coastline_new'] = new_coastline.coastline

    return old_mosaic
Example #2
0
def create_mosaic(dataset_in, clean_mask=None, no_data=-9999):
    """
    Description:
      Creates a most recent - oldest mosaic of the input dataset. If no clean mask is given,
      the 'cf_mask' variable must be included in the input dataset, as it will be used
      to create a clean mask
    -----
    Inputs:
      dataset_in (xarray.Dataset) - dataset retrieved from the Data Cube; should contain
        coordinates: time, latitude, longitude
        variables: variables to be mosaicked
        If user does not provide a clean_mask, dataset_in must also include the cf_mask
        variable
    Optional Inputs:
      clean_mask (nd numpy array with dtype boolean) - true for values user considers clean;
        if user does not provide a clean mask, one will be created using cfmask
      no_data (int/float) - no data pixel value; default: -9999
    Output:
      dataset_out (xarray.Dataset) - mosaicked data with
        coordinates: latitude, longitude
        variables: same as dataset_in
    """

    # Create clean_mask from cfmask if none given
    if not clean_mask:
        cfmask = dataset_in.cf_mask
        clean_mask = utilities.create_cfmask_clean_mask(cfmask)

    data_vars = dataset_in.data_vars  # Dict object with key as the name of the variable
    # and each value as the DataArray of that variable

    mosaic = collections.OrderedDict(
    )  # Dict to contain variable names as keys and
    # numpy arrays containing mosaicked data
    for key in data_vars:
        # Get raw data for current variable and mask the data
        data = data_vars[key].values
        masked = np.full(data.shape, no_data)
        masked[clean_mask] = data[clean_mask]
        out = np.full(masked.shape[1:], no_data)
        # Mosaic current variable (most recent - oldest)
        for index in reversed(range(len(clean_mask))):
            swap = np.reshape(np.in1d(out.reshape(-1), [no_data]), out.shape)
            out[swap] = masked[index][swap]
            mosaic[key] = (['latitude', 'longitude'], out)

    latitude = dataset_in.latitude
    longitude = dataset_in.longitude

    dataset_out = xr.Dataset(mosaic,
                             coords={
                                 'latitude': latitude,
                                 'longitude': longitude
                             })

    return dataset_out
Example #3
0
def create_mosaic(dataset_in, clean_mask=None, no_data=-9999):
    """
    Description:
      Creates a most recent - oldest mosaic of the input dataset. If no clean mask is given,
      the 'cf_mask' variable must be included in the input dataset, as it will be used
      to create a clean mask
    -----
    Inputs:
      dataset_in (xarray.Dataset) - dataset retrieved from the Data Cube; should contain
        coordinates: time, latitude, longitude
        variables: variables to be mosaicked
        If user does not provide a clean_mask, dataset_in must also include the cf_mask
        variable
    Optional Inputs:
      clean_mask (nd numpy array with dtype boolean) - true for values user considers clean;
        if user does not provide a clean mask, one will be created using cfmask
      no_data (int/float) - no data pixel value; default: -9999
    Output:
      dataset_out (xarray.Dataset) - mosaicked data with
        coordinates: latitude, longitude
        variables: same as dataset_in
    """

    # Create clean_mask from cfmask if none given
    if not clean_mask:
        cfmask = dataset_in.cf_mask
        clean_mask = utilities.create_cfmask_clean_mask(cfmask)

    data_vars = dataset_in.data_vars # Dict object with key as the name of the variable
                                     # and each value as the DataArray of that variable

    mosaic = collections.OrderedDict() # Dict to contain variable names as keys and
                                       # numpy arrays containing mosaicked data
    for key in data_vars:
        # Get raw data for current variable and mask the data
        data = data_vars[key].values
        masked = np.full(data.shape, no_data)
        masked[clean_mask] = data[clean_mask]
        out = np.full(masked.shape[1:], no_data)
        # Mosaic current variable (most recent - oldest)
        for index in reversed(range(len(clean_mask))):
            swap = np.reshape(np.in1d(out.reshape(-1), [no_data]),
                              out.shape)
            out[swap] = masked[index][swap]
            mosaic[key] = (['latitude', 'longitude'], out)

    latitude = dataset_in.latitude
    longitude = dataset_in.longitude

    dataset_out = xr.Dataset(mosaic,
                             coords={'latitude': latitude,
                                     'longitude': longitude})

    return dataset_out
def wofs_classify(dataset_in,
                  clean_mask=None,
                  no_data=-9999,
                  enforce_float64=False):
    """
    Description:
      Performs WOfS algorithm on given dataset. If no clean mask is given, the 'cf_mask'
      variable must be included in the input dataset, as it will be used to create a
      clean mask
    Assumption:
      - The WOfS algorithm is defined for Landsat 5/Landsat 7
    References:
      - Mueller, et al. (2015) "Water observations from space: Mapping surface water from
        25 years of Landsat imagery across Australia." Remote Sensing of Environment.
      - https://github.com/GeoscienceAustralia/eo-tools/blob/stable/eotools/water_classifier.py
    -----
    Inputs:
      dataset_in (xarray.Dataset) - dataset retrieved from the Data Cube; should contain
        coordinates: time, latitude, longitude
        variables: blue, green, red, nir, swir1, swir2
        If user does not provide a clean_mask, dataset_in must also include the cf_mask
        variable
    Optional Inputs:
      clean_mask (nd numpy array with dtype boolean) - true for values user considers clean;
        if user does not provide a clean mask, one will be created using cfmask
      no_data (int/float) - no data pixel value; default: -9999
      enforce_float64 (boolean) - flag to indicate whether or not to enforce float64 calculations;
        will use float32 if false
    Output:
      dataset_out (xarray.DataArray) - wofs water classification results: 0 - not water; 1 - water
    """
    def _band_ratio(a, b):
        """
        Calculates a normalized ratio index
        """
        return (a - b) / (a + b)

    def _run_regression(band1, band2, band3, band4, band5, band7):
        """
        Regression analysis based on Australia's training data
        TODO: Return type
        """

        # Compute normalized ratio indices
        ndi_52 = _band_ratio(band5, band2)
        ndi_43 = _band_ratio(band4, band3)
        ndi_72 = _band_ratio(band7, band2)

        #classified = np.ones(shape, dtype='uint8')

        classified = np.full(shape, no_data)

        # Start with the tree's left branch, finishing nodes as needed

        # Left branch
        r1 = ndi_52 <= -0.01

        r2 = band1 <= 2083.5
        classified[r1 & ~r2] = 0  #Node 3

        r3 = band7 <= 323.5
        _tmp = r1 & r2
        _tmp2 = _tmp & r3
        _tmp &= ~r3

        r4 = ndi_43 <= 0.61
        classified[_tmp2 & r4] = 1  #Node 6
        classified[_tmp2 & ~r4] = 0  #Node 7

        r5 = band1 <= 1400.5
        _tmp2 = _tmp & ~r5

        r6 = ndi_43 <= -0.01
        classified[_tmp2 & r6] = 1  #Node 10
        classified[_tmp2 & ~r6] = 0  #Node 11

        _tmp &= r5

        r7 = ndi_72 <= -0.23
        _tmp2 = _tmp & ~r7

        r8 = band1 <= 379
        classified[_tmp2 & r8] = 1  #Node 14
        classified[_tmp2 & ~r8] = 0  #Node 15

        _tmp &= r7

        r9 = ndi_43 <= 0.22
        classified[_tmp & r9] = 1  #Node 17
        _tmp &= ~r9

        r10 = band1 <= 473
        classified[_tmp & r10] = 1  #Node 19
        classified[_tmp & ~r10] = 0  #Node 20

        # Left branch complete; cleanup
        del r2, r3, r4, r5, r6, r7, r8, r9, r10
        gc.collect()

        # Right branch of regression tree
        r1 = ~r1

        r11 = ndi_52 <= 0.23
        _tmp = r1 & r11

        r12 = band1 <= 334.5
        _tmp2 = _tmp & ~r12
        classified[_tmp2] = 0  #Node 23

        _tmp &= r12

        r13 = ndi_43 <= 0.54
        _tmp2 = _tmp & ~r13
        classified[_tmp2] = 0  #Node 25

        _tmp &= r13

        r14 = ndi_52 <= 0.12
        _tmp2 = _tmp & r14
        classified[_tmp2] = 1  #Node 27

        _tmp &= ~r14

        r15 = band3 <= 364.5
        _tmp2 = _tmp & r15

        r16 = band1 <= 129.5
        classified[_tmp2 & r16] = 1  #Node 31
        classified[_tmp2 & ~r16] = 0  #Node 32

        _tmp &= ~r15

        r17 = band1 <= 300.5
        _tmp2 = _tmp & ~r17
        _tmp &= r17
        classified[_tmp] = 1  #Node 33
        classified[_tmp2] = 0  #Node 34

        _tmp = r1 & ~r11

        r18 = ndi_52 <= 0.34
        classified[_tmp & ~r18] = 0  #Node 36
        _tmp &= r18

        r19 = band1 <= 249.5
        classified[_tmp & ~r19] = 0  #Node 38
        _tmp &= r19

        r20 = ndi_43 <= 0.45
        classified[_tmp & ~r20] = 0  #Node 40
        _tmp &= r20

        r21 = band3 <= 364.5
        classified[_tmp & ~r21] = 0  #Node 42
        _tmp &= r21

        r22 = band1 <= 129.5
        classified[_tmp & r22] = 1  #Node 44
        classified[_tmp & ~r22] = 0  #Node 45

        # Completed regression tree

        return classified

    # Extract dataset bands needed for calculations
    blue = dataset_in.blue
    green = dataset_in.green
    red = dataset_in.red
    nir = dataset_in.nir
    swir1 = dataset_in.swir1
    swir2 = dataset_in.swir2

    # Create a clean mask from cfmask if the user does not provide one
    if not clean_mask:
        cfmask = dataset_in.cf_mask
        clean_mask = utilities.create_cfmask_clean_mask(cfmask)

    # Enforce float calculations - float64 if user specified, otherwise float32 will do
    dtype = blue.values.dtype  # This assumes all dataset bands will have
    # the same dtype (should be a reasonable
    # assumption)

    if enforce_float64:
        if dtype != 'float64':
            blue.values = blue.values.astype('float64')
            green.values = green.values.astype('float64')
            red.values = red.values.astype('float64')
            nir.values = nir.values.astype('float64')
            swir1.values = swir1.values.astype('float64')
            swir2.values = swir2.values.astype('float64')
    else:
        if dtype == 'float64':
            pass
        elif dtype != 'float32':
            blue.values = blue.values.astype('float32')
            green.values = green.values.astype('float32')
            red.values = red.values.astype('float32')
            nir.values = nir.values.astype('float32')
            swir1.values = swir1.values.astype('float32')
            swir2.values = swir2.values.astype('float32')

    shape = blue.values.shape
    classified = _run_regression(blue.values, green.values, red.values,
                                 nir.values, swir1.values, swir2.values)

    classified_clean = np.full(classified.shape, no_data)
    classified_clean[clean_mask] = classified[
        clean_mask]  # Contains data for clear pixels

    # Create xarray of data
    time = dataset_in.time
    latitude = dataset_in.latitude
    longitude = dataset_in.longitude

    data_array = xr.DataArray(classified_clean,
                              coords=[time, latitude, longitude],
                              dims=['time', 'latitude', 'longitude'])

    dataset_out = xr.Dataset({'wofs': data_array},
                             coords={
                                 'time': time,
                                 'latitude': latitude,
                                 'longitude': longitude
                             })

    return dataset_out