<commit_before># coding=utf-8
""" Module holding misc algorithms """
import ee
import ee.data
import math
from . import satellite as satmodule
from . import tools

if not ee.data._initialized:
    ee.Initialize()


def distance_to_mask(image, kernel=None, radius=1000, unit='meters',
                     scale=None, geometry=None, band_name='distance_to_mask',
                     normalize=False):
    """ Compute the distance to the mask in meters

    :param image: Image holding the mask
    :type image: ee.Image
    :param kernel: Kernel to use for computing the distance. By default uses
        euclidean
    :type kernel: ee.Kernel
    :param radius: radius for the kernel. Defaults to 1000
    :type radius: int
    :param unit: units for the kernel radius. Defaults to 'meters'
    :type unit: str
    :param scale: scale for reprojection. If None, will reproject on the fly
        (according to EE lazy computing)
    :type scale: int
    :param geometry: compute the distance only inside this geometry. If you
        want to compute the distance inside a clipped image, using this
        parameter will make the edges not be considered as a mask.
    :type geometry: ee.Geometry or ee.Feature
    :param band_name: name of the resulting band. Defaults to
        'distance_to_mask'
    :type band_name: str
    :param normalize: Normalize result (between 0 and 1)
    :type normalize: bool
    :return: A one band image with the distance to the mask
    :rtype: ee.Image
    """
    if not kernel:
        kernel = ee.Kernel.euclidean(radius, unit)

    # select first band
    image = image.select(0)

    # get mask
    mask = image.mask()
    inverse = mask.Not()

    if geometry:
        # manage geometry types
        if isinstance(geometry, (ee.Feature, ee.FeatureCollection)):
            geometry = geometry.geometry()

        inverse = inverse.clip(geometry)

    # Compute distance to the mask (inverse)
    distance = inverse.distance(kernel)

    if scale:
        proj = image.projection()
        distance = distance.reproject(proj.atScale(scale))

    # make mask to be the max distance
    dist_mask = distance.mask().Not().remap([0, 1], [0, radius])

    if geometry:
        dist_mask = dist_mask.clip(geometry)

    final = distance.unmask().add(dist_mask)

    if normalize:
        final = tools.image.parametrize(final, (0, radius), (0, 1))

    return final.rename(band_name)


def mask_cover(image, geometry=None, scale=1000,
               property_name='MASK_COVER', max_pixels=1e13):
    """ Percentage of masked pixels (masked/total * 100) as an Image property

    :param image: ee.Image holding the mask. If the image has more than
        one band, the first one will be used
    :type image: ee.Image
    :param geometry: the value will be computed inside this geometry. If None,
        will use image boundaries. If unbounded the result will be 0
    :type geometry: ee.Geometry or ee.Feature
    :param scale: the scale of the mask
    :type scale: int
    :param property_name: the name of the resulting property
    :type property_name: str
    :return: The same parsed image with a new property holding the mask cover
        percentage
    :rtype: ee.Image
    """
    # keep only first band
    image = image.select(0)

    # get projection
    projection = image.projection()

    # get band name
    band = ee.String(image.bandNames().get(0))

    # Make an image with all ones
    ones_i = ee.Image.constant(1).reproject(projection).rename(band)

    if not geometry:
        geometry = image.geometry()

    unbounded = geometry.isUnbounded()

    # manage geometry types
    if isinstance(geometry, (ee.Feature, ee.FeatureCollection)):
        geometry = geometry.geometry()

    # Get total number of pixels
    ones = ones_i.reduceRegion(
        reducer= ee.Reducer.count(),
        geometry= geometry,
        scale= scale,
        maxPixels= max_pixels).get(band)
    ones = ee.Number(ones)

    # select first band, unmask and get the inverse
    mask = image.mask()
    mask_not = mask.Not()
    image_to_compute = mask.updateMask(mask_not)

    # Get number of zeros in the given image
    zeros_in_mask =  image_to_compute.reduceRegion(
        reducer= ee.Reducer.count(),
        geometry= geometry,
        scale= scale,
        maxPixels= max_pixels).get(band)
    zeros_in_mask = ee.Number(zeros_in_mask)

    percentage = tools.number.trim_decimals(zeros_in_mask.divide(ones), 4)

    # Multiply by 100
    cover = percentage.multiply(100)

    # Return None if geometry is unbounded
    final = ee.Number(ee.Algorithms.If(unbounded, 0, cover))

    return image.set(property_name, final)


def euclidean_distance(image1, image2, name='distance'):
    """ Compute the Euclidean distance between two images. The image's bands
    is the dimension of the arrays.

    :param image1:
    :type image1: ee.Image
    :param image2:
    :type image2: ee.Image
    :return: a distance image
    :rtype: ee.Image
    """
    bandsi = image1.bandNames()

    proxy = tools.image.empty(0, bandsi)
    image1 = proxy.where(image1.gt(0), image1)
    image2 = proxy.where(image2.gt(0), image2)

    a = image1.subtract(image2)
    b = a.pow(2)
    c = b.reduce('sum')
    d = c.sqrt()

    return d.rename(name)


def sum_distance(image, collection):
    """ Compute de sum of all distances between the given image and the
    collection passed
    
    :param image: 
    :param collection:
    :return: 
    """
    condition = isinstance(collection, ee.ImageCollection)

    if condition:
        collection = collection.toList(collection.size())

    accum = ee.Image(0).rename('sumdist')

    def over_rest(im, ini):
        ini = ee.Image(ini)
        im = ee.Image(im)
        dist = ee.Image(euclidean_distance(image, im)).rename('sumdist')
        return ini.add(dist)

    return ee.Image(collection.iterate(over_rest, accum))


def pansharpen_kernel(image, pan, rgb=None, kernel=None):
    """
    Compute the per-pixel means of the unsharpened bands
    source: https://gis.stackexchange.com/questions/296615/pansharpen-landsat-mosaic-in-google-earth-engine

    :param pan: the name of the panchromatic band
    :type pan: str
    :param rgb: the red green blue bands
    :type rgb: tuple or list
    :param kernel: the kernel to reduce neighbors
    :type kernel: ee.Kernel
    :rtype: ee.Image
    """
    if not kernel:
        kernel = ee.Kernel.square(90, 'meters')
    if not rgb:
        rgb = ['red', 'green', 'blue']

    if not pan:
        pan = 'pan'

    bgr = image.select(rgb)
    pani = image.select(pan)
    bgr_mean = bgr.reduce('mean').rename('mean')
    # Compute the aggregate mean of the unsharpened bands and the pan band
    mean_values = pani.addBands(bgr_mean).reduceNeighborhood(
        ee.Reducer.mean(),
        kernel
    )
    gain = mean_values.select('mean_mean').divide(
        mean_values.select('{}_mean'.format(pan)))
    return bgr.divide(bgr_mean).multiply(pani).multiply(gain)


def pansharpen_ihs_fusion(image, pan=None, rgb=None):
    """
    HSV-based Pan-Sharpening
    source: https://gis.stackexchange.com/questions/296615/pansharpen-landsat-mosaic-in-google-earth-engine

    :param image:
    :type image: ee.Image
    the name of the panchromatic band
    :type pan: str
    :param rgb: the red green blue bands
    :type rgb: tuple or list
    :rtype: ee.Image
    """
    if not rgb:
        rgb = ['red', 'green', 'blue']

    if not pan:
        pan = 'pan'

    rgb = image.select(rgb)
    pan = image.select(pan)
    # Convert to HSV, swap in the pan band, and convert back to RGB.
    huesat = rgb.rgbToHsv().select('hue', 'saturation')
    upres = ee.Image.cat(huesat, pan).hsvToRgb()
    return image.addBands(upres)


class Landsat(object):

    @staticmethod
    def harmonization(image, sr=True, blue='B2', green='B3', red='B4', nir='B5',
                      swir1='B6', swir2='B7'):
        """ Slope and intercept

        <NAME>., <NAME>., <NAME>., <NAME>., <NAME>.,
        <NAME>, <NAME>., 2016, Characterization of Landsat-7 to
        Landsat-8 reflective wavelength and normalized difference vegetation
        index continuity, Remote Sensing of Environment, 185, 57-70.
        (http:##dx.doi.org/10.1016/j.rse.2015.12.024) Table 2 -<commit_msg>rescale Landsat<commit_after># coding=utf-8
""" Module holding misc algorithms """
import ee
import ee.data
import math
from . import satellite as satmodule
from . import tools

if not ee.data._initialized:
    ee.Initialize()


def distance_to_mask(image, kernel=None, radius=1000, unit='meters',
                     scale=None, geometry=None, band_name='distance_to_mask',
                     normalize=False):
    """ Compute the distance to the mask in meters

    :param image: Image holding the mask
    :type image: ee.Image
    :param kernel: Kernel to use for computing the distance. By default uses
        euclidean
    :type kernel: ee.Kernel
    :param radius: radius for the kernel. Defaults to 1000
    :type radius: int
    :param unit: units for the kernel radius. Defaults to 'meters'
    :type unit: str
    :param scale: scale for reprojection. If None, will reproject on the fly
        (according to EE lazy computing)
    :type scale: int
    :param geometry: compute the distance only inside this geometry. If you
        want to compute the distance inside a clipped image, using this
        parameter will make the edges not be considered as a mask.
    :type geometry: ee.Geometry or ee.Feature
    :param band_name: name of the resulting band. Defaults to
        'distance_to_mask'
    :type band_name: str
    :param normalize: Normalize result (between 0 and 1)
    :type normalize: bool
    :return: A one band image with the distance to the mask
    :rtype: ee.Image
    """
    if not kernel:
        kernel = ee.Kernel.euclidean(radius, unit)

    # select first band
    image = image.select(0)

    # get mask
    mask = image.mask()
    inverse = mask.Not()

    if geometry:
        # manage geometry types
        if isinstance(geometry, (ee.Feature, ee.FeatureCollection)):
            geometry = geometry.geometry()

        inverse = inverse.clip(geometry)

    # Compute distance to the mask (inverse)
    distance = inverse.distance(kernel)

    if scale:
        proj = image.projection()
        distance = distance.reproject(proj.atScale(scale))

    # make mask to be the max distance
    dist_mask = distance.mask().Not().remap([0, 1], [0, radius])

    if geometry:
        dist_mask = dist_mask.clip(geometry)

    final = distance.unmask().add(dist_mask)

    if normalize:
        final = tools.image.parametrize(final, (0, radius), (0, 1))

    return final.rename(band_name)


def mask_cover(image, geometry=None, scale=1000,
               property_name='MASK_COVER', max_pixels=1e13):
    """ Percentage of masked pixels (masked/total * 100) as an Image property

    :param image: ee.Image holding the mask. If the image has more than
        one band, the first one will be used
    :type image: ee.Image
    :param geometry: the value will be computed inside this geometry. If None,
        will use image boundaries. If unbounded the result will be 0
    :type geometry: ee.Geometry or ee.Feature
    :param scale: the scale of the mask
    :type scale: int
    :param property_name: the name of the resulting property
    :type property_name: str
    :return: The same parsed image with a new property holding the mask cover
        percentage
    :rtype: ee.Image
    """
    # keep only first band
    image = image.select(0)

    # get projection
    projection = image.projection()

    # get band name
    band = ee.String(image.bandNames().get(0))

    # Make an image with all ones
    ones_i = ee.Image.constant(1).reproject(projection).rename(band)

    if not geometry:
        geometry = image.geometry()

    unbounded = geometry.isUnbounded()

    # manage geometry types
    if isinstance(geometry, (ee.Feature, ee.FeatureCollection)):
        geometry = geometry.geometry()

    # Get total number of pixels
    ones = ones_i.reduceRegion(
        reducer= ee.Reducer.count(),
        geometry= geometry,
        scale= scale,
        maxPixels= max_pixels).get(band)
    ones = ee.Number(ones)

    # select first band, unmask and get the inverse
    mask = image.mask()
    mask_not = mask.Not()
    image_to_compute = mask.updateMask(mask_not)

    # Get number of zeros in the given image
    zeros_in_mask =  image_to_compute.reduceRegion(
        reducer= ee.Reducer.count(),
        geometry= geometry,
        scale= scale,
        maxPixels= max_pixels).get(band)
    zeros_in_mask = ee.Number(zeros_in_mask)

    percentage = tools.number.trim_decimals(zeros_in_mask.divide(ones), 4)

    # Multiply by 100
    cover = percentage.multiply(100)

    # Return None if geometry is unbounded
    final = ee.Number(ee.Algorithms.If(unbounded, 0, cover))

    return image.set(property_name, final)


def euclidean_distance(image1, image2, name='distance'):
    """ Compute the Euclidean distance between two images. The image's bands
    is the dimension of the arrays.

    :param image1:
    :type image1: ee.Image
    :param image2:
    :type image2: ee.Image
    :return: a distance image
    :rtype: ee.Image
    """
    bandsi = image1.bandNames()

    proxy = tools.image.empty(0, bandsi)
    image1 = proxy.where(image1.gt(0), image1)
    image2 = proxy.where(image2.gt(0), image2)

    a = image1.subtract(image2)
    b = a.pow(2)
    c = b.reduce('sum')
    d = c.sqrt()

    return d.rename(name)


def sum_distance(image, collection):
    """ Compute de sum of all distances between the given image and the
    collection passed
    
    :param image: 
    :param collection:
    :return: 
    """
    condition = isinstance(collection, ee.ImageCollection)

    if condition:
        collection = collection.toList(collection.size())

    accum = ee.Image(0).rename('sumdist')

    def over_rest(im, ini):
        ini = ee.Image(ini)
        im = ee.Image(im)
        dist = ee.Image(euclidean_distance(image, im)).rename('sumdist')
        return ini.add(dist)

    return ee.Image(collection.iterate(over_rest, accum))


def pansharpen_kernel(image, pan, rgb=None, kernel=None):
    """
    Compute the per-pixel means of the unsharpened bands
    source: https://gis.stackexchange.com/questions/296615/pansharpen-landsat-mosaic-in-google-earth-engine

    :param pan: the name of the panchromatic band
    :type pan: str
    :param rgb: the red green blue bands
    :type rgb: tuple or list
    :param kernel: the kernel to reduce neighbors
    :type kernel: ee.Kernel
    :rtype: ee.Image
    """
    if not kernel:
        kernel = ee.Kernel.square(90, 'meters')
    if not rgb:
        rgb = ['red', 'green', 'blue']

    if not pan:
        pan = 'pan'

    bgr = image.select(rgb)
    pani = image.select(pan)
    bgr_mean = bgr.reduce('mean').rename('mean')
    # Compute the aggregate mean of the unsharpened bands and the pan band
    mean_values = pani.addBands(bgr_mean).reduceNeighborhood(
        ee.Reducer.mean(),
        kernel
    )
    gain = mean_values.select('mean_mean').divide(
        mean_values.select('{}_mean'.format(pan)))
    return bgr.divide(bgr_mean).multiply(pani).multiply(gain)


def pansharpen_ihs_fusion(image, pan=None, rgb=None):
    """
    HSV-based Pan-Sharpening
    source: https://gis.stackexchange.com/questions/296615/pansharpen-landsat-mosaic-in-google-earth-engine

    :param image:
    :type image: ee.Image
    the name of the panchromatic band
    :type pan: str
    :param rgb: the red green blue bands
    :type rgb: tuple or list
    :rtype: ee.Image
    """
    if not rgb:
        rgb = ['red', 'green', 'blue']

    if not pan:
        pan = 'pan'

    rgb = image.select(rgb)
    pan = image.select(pan)
    # Convert to HSV, swap in the pan band, and convert back to RGB.
    huesat = rgb.rgbToHsv().select('hue', 'saturation')
    upres = ee.Image.cat(huesat, pan).hsvToRgb()
    return image.addBands(upres)


class Landsat(object):

    @staticmethod
    def _rescale(image, bands=None, thermal_bands=None, original='TOA',
                 to='SR', number='all'):
        """ Rescaling logic """
        if not bands:
            bands = ['B1','B2','B3','B4','B5','B6','B7']
        bands = ee.List(bands)

        if not thermal_bands:
            thermal_bands = ['B10', 'B11']
        thermal_bands = ee.List(thermal_bands)

        allbands = bands.cat(thermal_bands)

        if number == '8':
            max_raw = 65535
        else:
            max_raw = 255

        if original == 'TOA' and to == 'SR':
            scaled = image.select(bands).multiply(10000).toInt16()
            scaled_thermal = image.select(thermal_bands).multiply(10).toInt16()
        elif original == 'SR' and to == 'TOA':
            scaled = image.select(bands).toFloat().divide(10000)
            scaled_thermal = image.select(thermal_bands).toFloat().divide(10)
        elif original == 'TOA' and to == 'RAW':
            scaled = tools.image.parametrize(image.select(bands), (0, 1),
                                             (0, max_raw))
            scaled_thermal = tools.image.parametrize(image.select(bands), (0, 1),
                                                     (0, max_raw)).multiply(1000)

        original_bands = image.bandNames()

        rest_bands = tools.ee_list.difference(original_bands, allbands)

        rest_image = image.select(rest_bands)

        return rest_image.addBands(scaled).addBands(scaled_thermal)

    @staticmethod
    def rescale_toa_sr(image, bands=None, thermal_bands=None):
        """ Re-scale a TOA Landsat image to match the data type of SR Landsat
        image

        :param image: a Landsat TOA image
        :type image: ee.Image
        :param bands: bands to rescale by 10000. If None will use the bands for
            Landsat 8 (['B1','B2','B3','B4','B5','B6','B7']).
        :type bands: list
        :param thermal_bands: bands to rescale by 100. Defaults to
            ['B10', 'B11']
        :type thermal_bands: list
        :rtype: ee.Image
        """
        return Landsat._rescale(image, bands, thermal_bands, 'TOA', 'SR')

    @staticmethod
    def rescale_sr_toa(image, bands=None, thermal_bands=None):
        """ Re-scale a TOA Landsat image to match the data type of SR Landsat
        image

        :param image: a Landsat TOA image
        :type image: ee.Image
        :param bands: bands to rescale by 10000. If None will use the bands for
            Landsat 8 (B[1-7]).
        :type bands: list
        :param thermal_bands: bands to rescale by 100. Defaults to
            ['B10', 'B11']
        :type thermal_bands: list
        :rtype: ee.Image
        """
        return Landsat._rescale(image, bands, thermal_bands, 'SR', 'TOA')

    @staticmethod
    def harmonization(image, sr=True, blue='B2', green='B3', red='B4', nir='B5',
                      swir1='B6', swir2='B7'):
        """ Harmonization of Landsat 8 images to be consistant with
        Landsat 7 images

        <NAME>., <NAME>., <NAME>., <NAME>., <NAME>.,
        <NAME>, <NAME>., 2016, Characterization of Landsat-7 to
        Landsat-8 reflective wavelength and normalized difference vegetation
        index continuity, Remote Sensing of Environment, 185, 57-70.
        (http://dx.doi.org/10.1016/j.rse.2015.12.024) Table 2 -
{'fraction_non_alphanumeric': 0.10710427491126087, 'fraction_numerical': 0.015792993466742115, 'mean_word_length': 2.6630864895421142, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2934751', 'n_tokens_mistral': 6396, 'n_tokens_neox': 5943, 'n_words': 1810}