예제 #1
0
def gdal_save(arr, src_file, filename, eType, options=[]):
    """
    Save the 2D ndarray arr to filename using the same metadata as the
    given source file.  Returns the new gdal file object in case
    additional operations are desired.
    """
    if isinstance(arr, list):
        numberOfBands = len(arr)
    else:
        numberOfBands = 1
        arr = [arr]
    driver = src_file.GetDriver()
    if driver.GetMetadata().get(gdal.DCAP_CREATE) != "YES":
        raise RuntimeError(
            "Driver {} does not support Create().".format(driver))
    arr_file = driver.Create(
        filename,
        xsize=arr[0].shape[1],
        ysize=arr[0].shape[0],
        bands=numberOfBands,
        eType=eType,
        options=options,
    )
    gdalnumeric.CopyDatasetInfo(src_file, arr_file)
    for i, a in enumerate(arr):
        arr_file.GetRasterBand(i + 1).WriteArray(a)
    return arr_file
예제 #2
0
def OpenArray(array, prototype_ds=None, xoff=0, yoff=0):
    #ds = gdal.Open( gdalnumeric.GetArrayFilename(array) )
    ds = gdal_array.OpenNumPyArray(array)
    #ds=gdal_array.OpenArray(array)
    if ds is not None and prototype_ds is not None:
        if type(prototype_ds).__name__ == 'str':
            prototype_ds = gdal.Open(prototype_ds)
        if prototype_ds is not None:
            gdalnumeric.CopyDatasetInfo(prototype_ds, ds, xoff=xoff, yoff=yoff)
    return ds
예제 #3
0
파일: gvshell.py 프로젝트: lmarabi/tareeg
def display(array, prototype_name=None):
    import Numeric
    if len(Numeric.shape(array)) == 1:
        array = Numeric.reshape(array, (1, Numeric.shape(array)[0]))

    array_name = gdalnumeric.GetArrayFilename(array)
    ds = gview.manager.get_dataset(array_name)
    if prototype_name is not None:
        prototype_ds = gdal.Open(prototype_name)
        gdalnumeric.CopyDatasetInfo(prototype_ds, ds)

    gview.app.file_open_by_name(array_name)
    def OpenArray_T(array, prototype_ds=None, xoff=0, yoff=0):
        """
        EDIT: this is basically an overloaded
        version of the gdal_array.OpenArray passing in xoff, yoff explicitly
        so we can pass these params off to CopyDatasetInfo
        """

        #ds = gdal.Open(gdalnumeric.GetArrayFilename(array))
        ds = gdal_array.OpenArray(array)

        if ds is not None and prototype_ds is not None:
            if type(prototype_ds).__name__ == 'str':
                prototype_ds = gdal.Open(prototype_ds)
            if prototype_ds is not None:
                gdalnumeric.CopyDatasetInfo(
                    prototype_ds, ds, xoff=xoff, yoff=yoff)
        return ds
예제 #5
0
def gdal_clip(raster_input, raster_output, polygon_json, nodata=0):
    """
    This function will subset a raster by a vector polygon.
    Adapted from the GDAL/OGR Python Cookbook at
    https://pcjericks.github.io/py-gdalogr-cookbook

    :param raster_input: raster input filepath
    :param raster_output: raster output filepath
    :param polygon_json: polygon as geojson string
    :param nodata: nodata value for output raster file
    :return: GDAL Dataset
    """
    def image_to_array(i):
        """
        Converts a Python Imaging Library array to a
        gdalnumeric image.
        """
        a = gdalnumeric.numpy.fromstring(i.tobytes(), 'b')
        a.shape = i.im.size[1], i.im.size[0]
        return a

    def world_to_pixel(geoMatrix, x, y):
        """
        Uses a gdal geomatrix (gdal.GetGeoTransform()) to calculate
        the pixel location of a geospatial coordinate
        """
        ulX = geoMatrix[0]
        ulY = geoMatrix[3]
        xDist = geoMatrix[1]
        pixel = int((x - ulX) / xDist)
        line = int((ulY - y) / xDist)
        return (pixel, line)

    src_image = get_dataset(raster_input)
    # Load the source data as a gdalnumeric array
    src_array = src_image.ReadAsArray()
    src_dtype = src_array.dtype

    # Also load as a gdal image to get geotransform
    # (world file) info
    geo_trans = src_image.GetGeoTransform()
    nodata_values = []
    for i in range(src_image.RasterCount):
        nodata_value = src_image.GetRasterBand(i + 1).GetNoDataValue()
        if not nodata_value:
            nodata_value = nodata
        nodata_values.append(nodata_value)

    # Create an OGR layer from a boundary GeoJSON geometry string
    if type(polygon_json) == dict:
        polygon_json = json.dumps(polygon_json)
    poly = ogr.CreateGeometryFromJson(polygon_json)

    # Convert the layer extent to image pixel coordinates
    min_x, max_x, min_y, max_y = poly.GetEnvelope()
    ul_x, ul_y = world_to_pixel(geo_trans, min_x, max_y)
    lr_x, lr_y = world_to_pixel(geo_trans, max_x, min_y)

    # Calculate the pixel size of the new image
    px_width = int(lr_x - ul_x)
    px_height = int(lr_y - ul_y)

    clip = src_array[ul_y:lr_y, ul_x:lr_x]

    # create pixel offset to pass to new image Projection info
    xoffset = ul_x
    yoffset = ul_y

    # Create a new geomatrix for the image
    geo_trans = list(geo_trans)
    geo_trans[0] = min_x
    geo_trans[3] = max_y

    # Map points to pixels for drawing the
    # boundary on a blank 8-bit,
    # black and white, mask image.
    raster_poly = Image.new("L", (px_width, px_height), 1)
    rasterize = ImageDraw.Draw(raster_poly)
    geometry_count = poly.GetGeometryCount()
    for i in range(0, geometry_count):
        points = []
        pixels = []
        pts = poly.GetGeometryRef(i)
        if pts.GetPointCount() == 0:
            pts = pts.GetGeometryRef(0)
        for p in range(pts.GetPointCount()):
            points.append((pts.GetX(p), pts.GetY(p)))
        for p in points:
            pixels.append(world_to_pixel(geo_trans, p[0], p[1]))
        rasterize.polygon(pixels, 0)
    mask = image_to_array(raster_poly)

    # Clip the image using the mask
    clip = gdalnumeric.numpy.choose(mask,
                                    (clip, nodata_value)).astype(src_dtype)

    # create output raster
    raster_band = raster_input.GetRasterBand(1)
    output_driver = gdal.GetDriverByName('MEM')
    output_dataset = output_driver.Create('', clip.shape[1], clip.shape[0],
                                          raster_input.RasterCount,
                                          raster_band.DataType)
    output_dataset.SetGeoTransform(geo_trans)
    output_dataset.SetProjection(raster_input.GetProjection())
    gdalnumeric.CopyDatasetInfo(raster_input,
                                output_dataset,
                                xoff=xoffset,
                                yoff=yoffset)
    bands = raster_input.RasterCount
    if bands > 1:
        for i in range(bands):
            outBand = output_dataset.GetRasterBand(i + 1)
            outBand.SetNoDataValue(nodata_values[i])
            outBand.WriteArray(clip[i])
    else:
        outBand = output_dataset.GetRasterBand(1)
        outBand.SetNoDataValue(nodata_values[0])
        outBand.WriteArray(clip)

    if raster_output:
        output_driver = gdal.GetDriverByName('GTiff')
        outfile = output_driver.CreateCopy(raster_output, output_dataset,
                                           False)
        logger.debug(str(outfile))
        outfile = None

    return output_dataset
예제 #6
0
# print(str(dataset.RasterCount)) # vypis poctu pasem v rastru/datasetu
# nacteni hodnost z rastru/pasma do pole
hodnoty = band.ReadAsArray()
# print(hodnoty)
# print(hodnoty.shape)
# zjisteni poctu sloupcu a radku rastru/datasetu
sloupce = dataset.RasterXSize
radky = dataset.RasterYSize
# vytvoreni pole o stejnem poctu sloupcu a radku, jako ma vstup - bude obsahovat same 0
noveHodnoty = np.zeros((radky, sloupce))
# cykly pro prochazeni rastru od leveho horniho okraje po radcich a sloupcich
# cykly vynechavaji okrajove radky a sloupce (zacinaji az od 1, ne 0 a konci na predposlednim radku/sloupci)
#   - aby se pri vymezeni okoli (3x3 bunky) zpracovavanych bunek nedostal za okraj rastru

noveHodnoty = filtrace_hrany(hodnoty, radky, sloupce)
## ulozeni vyfiltrovaneho rastru (pole noveHodnoty) do GeoTiffu
# urceni driver podle formatu, do ktereho se bude ukladat
driver = gdal.GetDriverByName("GTiff")
# vytvoreni (prazdneho) datasetu s parametry kam se bude ukladat, jakou bude mit velikost, kolik pasem a datovy typ hodnot
# posledni parametr lze zadat i v podobe: band.DataType - prevezme se datovy typ ze vstupni vrstvy
dsOut = driver.Create(
    r"C:\Users\admin\Documents\vsb\programovani\teren_hrany.tiff", sloupce,
    radky, 1, gdal.GDT_Float32)
# zkopirovani prostorove reference rastu ze vstupniho datasetu do vystupniho
gdalnumeric.CopyDatasetInfo(dataset, dsOut)
# ziskani pasma vystupu
bandOut = dsOut.GetRasterBand(1)
# zapis hodnot z pole noveHodnoty do vystupniho pasma
BandWriteArray(bandOut, noveHodnoty)
# dokonceni zapisu
bandOut.FlushCache()
예제 #7
0
    def raster2array(self, filename):

        decomposition = [
            '111', '112', '113', '121', '122', '123', '131', '132', '133',
            '211', '212', '213', '221', '222', '223', '231', '232', '233',
            '311', '312', '313', '321', '322', '323', '331', '332', '333'
        ]

        self.processStatus.setText('Loading bands...')
        self.progressBar.setValue(5)
        # Get file, open with GDAL and separate rasters
        fileDir = os.path.dirname(os.path.realpath('__file__'))
        source = gdal.Open(filename)
        K = np.array(source.GetRasterBand(1).ReadAsArray())
        Th = np.array(source.GetRasterBand(2).ReadAsArray())
        U = np.array(source.GetRasterBand(3).ReadAsArray())

        # Create zeros for all elements on low, mid, high
        K_ref = np.zeros(K.shape, dtype=np.uint8)
        Th_ref = np.zeros(Th.shape, dtype=np.uint8)
        U_ref = np.zeros(U.shape, dtype=np.uint8)

        self.processStatus.setText('Decomposing potassium...')
        self.progressBar.setValue(15)

        # Actual separation loop
        for i, j in np.ndindex(K.shape):
            if (K[i, j] <= 85):
                K_ref[i, j] = 1
            if (K[i, j] > 85 and K[i, j] <= 170):
                K_ref[i, j] = 128
            if (K[i, j] > 170):
                K_ref[i, j] = 255

        self.processStatus.setText('Decomposing thorium...')
        self.progressBar.setValue(25)

        # Actual separation loop
        for i, j in np.ndindex(Th.shape):
            if (Th[i, j] <= 85):
                Th_ref[i, j] = 1
            if (Th[i, j] > 85 and Th[i, j] <= 170):
                Th_ref[i, j] = 128
            if (Th[i, j] > 170):
                Th_ref[i, j] = 255
        del Th

        self.processStatus.setText('Decomposing uranium...')
        self.progressBar.setValue(35)

        # Actual separation loop
        for i, j in np.ndindex(U.shape):
            if (U[i, j] <= 85):
                U_ref[i, j] = 1
            if (U[i, j] > 85 and U[i, j] <= 170):
                U_ref[i, j] = 128
            if (U[i, j] > 170):
                U_ref[i, j] = 255
        del U

        progressNumber = 1
        self.processStatus.setText('Exporting 1/27...')

        for i in decomposition:
            progressValue = 35 + 2.4
            outputName = filename + '_' + i + '.tif'
            outputPath = os.path.join(fileDir, outputName)
            self.progressBar.setValue(progressValue)
            R = np.zeros(K.shape, dtype=np.uint8)
            G = np.zeros(K.shape, dtype=np.uint8)
            B = np.zeros(K.shape, dtype=np.uint8)
            progressText = 'Exporting' + str(progressNumber) + '/27...'

            for w, x in np.ndindex(K.shape):
                if (K_ref[w, x] == self.colortransform(i[0])
                        and Th_ref[w, x] == self.colortransform(i[1])
                        and U_ref[w, x] == self.colortransform(i[2])):
                    R[w, x] = self.colortransform(i[0])
                    G[w, x] = self.colortransform(i[1])
                    B[w, x] = self.colortransform(i[2])
            # Create Gtiff driver with RGB Opts
            dst_ds = gdal.GetDriverByName('GTiff').Create(
                outputName,
                K.shape[1],
                K.shape[0],
                3,
                gdal.GDT_Byte,
                options=[
                    'PHOTOMETRIC=RGB',
                    'PROFILE=GeoTIFF',
                ])
            # Copy georeferencing info
            gdalnumeric.CopyDatasetInfo(source, dst_ds)
            # Write arrays
            dst_ds.GetRasterBand(1).WriteArray(R)
            dst_ds.GetRasterBand(1).SetNoDataValue(0)
            dst_ds.FlushCache()
            dst_ds.GetRasterBand(2).WriteArray(G)
            dst_ds.GetRasterBand(2).SetNoDataValue(0)
            dst_ds.FlushCache()
            dst_ds.GetRasterBand(3).WriteArray(B)
            dst_ds.GetRasterBand(3).SetNoDataValue(0)
            dst_ds.FlushCache()

            if (self.checkedBox == 1):
                src_ds = dst_ds
                proj = osr.SpatialReference(wkt=src_ds.GetProjection())
                srcband = src_ds.GetRasterBand(2)
                dst_layername = i
                drv = ogr.GetDriverByName("ESRI Shapefile")
                dst_ds = drv.CreateDataSource(dst_layername + ".shp")
                #gdalnumeric.CopyDatasetInfo(src_ds, dst_ds)
                dst_layer = dst_ds.CreateLayer(dst_layername, proj)
                gdal.Polygonize(srcband,
                                None,
                                dst_layer,
                                -1, [],
                                callback=None)
                dst_ds.FlushCache()

            dst_ds = None
        self.processStatus.setText('')
        self.progressBar.setValue(100)
        QMessageBox.about(self, "Ternary Decomposer", "It's done!")
예제 #8
0
def write_raster(array, template, filename):
    driver = gdal.GetDriverByName("GTiff")
    raster_out = driver.Create(filename, template.RasterXSize, template.RasterYSize, 1, template.GetRasterBand(1).DataType)
    gdalnumeric.CopyDatasetInfo(template,raster_out)
    bandOut=raster_out.GetRasterBand(1)
    gdalnumeric.BandWriteArray(bandOut, array)
예제 #9
0
    def interpolatePoints(self):

        if self.datafolder is None:
            return

        outfolder = self.datafolder + '/Surface'

        # Interpolate each layer by using TIN interpolation
        nlayers = int(self.dlg.lineEdit_22.text())
        xblock = int(self.dlg.lineEdit_13.text())
        yblock = int(self.dlg.lineEdit_14.text())
        kzthresh = int(self.dlg.lineEdit_16.text())
        kvthresh = int(self.dlg.lineEdit_15.text())
        res = int(self.dlg.lineEdit_17.text())
        smooth = int(self.dlg.lineEdit_18.text())

        for k in xrange(1, nlayers + 1):
            ptfile = outfolder + '/surface-%s.txt' % k
            outfile = outfolder + '/surface-%s_flt.txt' % k
            if not os.path.isfile(outfile):
                filterPoints(ptfile, outfile, xblock, yblock, kvthresh,
                             kzthresh)
            ptfile = outfile
            outfile = outfolder + '/surface-%s_flt.tif' % k
            if not os.path.isfile(outfile):
                interpolatePoints(ptfile, outfile, res, smooth)
        1  # find out which is the top surface layer
        mdtm = np.zeros(nlayers)
        for k in xrange(1, nlayers + 1):
            tiffile = outfolder + '/surface-%s_flt.tif' % k
            ds = gdal.Open(tiffile, gdal.GA_ReadOnly)
            data = ds.GetRasterBand(1).ReadAsArray()
            maskeddata = np.ma.masked_invalid(data)
            mdtm[k - 1] = maskeddata.mean()

        surfidx = np.argmax(mdtm) + 1
        tiffile = outfolder + '/surface-%s_flt.tif' % surfidx
        ds1 = gdal.Open(tiffile, gdal.GA_ReadOnly)
        data1 = ds1.GetRasterBand(1).ReadAsArray()

        for k in xrange(1, nlayers + 1):
            if k != surfidx:
                tiffile = outfolder + '/surface-%s_flt.tif' % k
                ds2 = gdal.Open(tiffile, gdal.GA_ReadOnly)
                data2 = ds2.GetRasterBand(1).ReadAsArray()

                driver = gdal.GetDriverByName('GTiff')
                outfile = outfolder + '/surface-%s_flt_depth.tif' % k
                dsOut = driver.Create(outfile, ds1.RasterXSize,
                                      ds1.RasterYSize, 1, gdal.GDT_Float32)
                gdalnumeric.CopyDatasetInfo(ds1, dsOut)
                dsOut.GetRasterBand(1).WriteArray(
                    (data1 - data2) / np.sqrt(3.14))
                dsOut.GetRasterBand(1).SetNoDataValue(-32768)
                dsOut = None
        ds1 = None

        for k in xrange(1, nlayers + 1):
            print k
            if k == surfidx:
                outfile = outfolder + '/surface-%s_flt.tif' % surfidx
            else:
                outfile = outfolder + '/surface-%s_flt_depth.tif' % k
                plotGeotiff(outfile)

            ### Load
            print outfile
            fileInfo = QFileInfo(outfile)
            path = fileInfo.filePath()
            baseName = fileInfo.baseName()

            layer = QgsRasterLayer(path, baseName)
            if layer.isValid() is True:
                QgsMapLayerRegistry.instance().addMapLayer(layer)
예제 #10
0
def main(args):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('--rgb_image',
                        required=True,
                        help='File path to orthorectified RGB image')
    parser.add_argument('--msi_image',
                        required=True,
                        help='File path to MSI image (8 channels)')
    parser.add_argument('--dsm', required=True, help='File path to DSM')
    parser.add_argument('--dtm', required=True, help='File path to DTM')
    parser.add_argument(
        '--model_path',
        help='Combined directory and prefix of the network model parameters')
    parser.add_argument(
        '--model_dir',
        help='Directory containing the network model parameters')
    parser.add_argument('--model_prefix',
                        help='File prefix of the network model parameters')
    parser.add_argument('--save_dir',
                        default='building_seg/',
                        help='Folder to save result')
    parser.add_argument('--no_NDVI',
                        action='store_true',
                        help='Disable the use of NDVI')
    parser.add_argument('--output_tif',
                        action='store_true',
                        help="Save the output as GeoTIFF")
    parser.add_argument('--gpu-id',
                        default='0',
                        type=str,
                        help='id(s) for CUDA_VISIBLE_DEVICES')

    np.set_printoptions(precision=2)

    args = parser.parse_args(args)

    # Accept either combined model directory/prefix or separate directory and prefix
    if args.model_path is None:
        if args.model_dir is None or args.model_prefix is None:
            raise RuntimeError('Model directory and prefix are required')
        args.model_path = os.path.join(args.model_dir, args.model_prefix)
    elif args.model_dir is not None or args.model_prefix is not None:
        raise RuntimeError(
            'The model_dir and model_prefix arguments cannot be specified when '
            'model_path is specified')

    os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu_id

    training_stride = 4

    test_img_data, test_dhm, test_NDVI, dsm = read_data_test(args)
    # resize image
    test_img_data = cv2.resize(test_img_data, (2048, 2048))
    test_dhm_data = cv2.resize(
        test_dhm, (test_img_data.shape[1], test_img_data.shape[0]))
    if not args.no_NDVI:
        test_NDVI_data = cv2.resize(
            test_NDVI, (test_img_data.shape[1], test_img_data.shape[0]))

    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    sess = tf.Session(config=config)

    # Build the model
    if not args.no_NDVI:
        img_place_holder = tf.placeholder(tf.float32, [None, None, None, 5])
    else:
        img_place_holder = tf.placeholder(tf.float32, [None, None, None, 4])

    with tf.contrib.slim.arg_scope(inception_v1.inception_v1_arg_scope()):
        logits, _ = inception_v1.inception_v1(img_place_holder, no_bn=False)
        test_logits, _ = inception_v1.inception_v1(img_place_holder,
                                                   reuse=True,
                                                   is_training=False,
                                                   no_bn=False)

    restorer = tf.train.Saver()
    restorer.restore(sess, '{}'.format(args.model_path))

    # show with different threshold
    img = combine_imagery(test_img_data, test_dhm_data, test_NDVI_data)
    tmp_logits = sess.run([test_logits], feed_dict={img_place_holder: img})

    pred = tmp_logits[0]  # np.argmax(tmp_logits[0],axis = 1)
    pred = pred.reshape(int(test_img_data.shape[0] / training_stride),
                        int(test_img_data.shape[1] / training_stride))

    resize_depth_data = cv2.resize(test_dhm_data,
                                   (pred.shape[0], pred.shape[1]),
                                   interpolation=cv2.INTER_NEAREST)

    predict_img = np.zeros((int(test_img_data.shape[0] / training_stride),
                            int(test_img_data.shape[1] / training_stride)),
                           dtype=np.uint8)

    predict_img[pred > 0.0] = 255
    pred[resize_depth_data > 200] = 1.0
    predict_img[resize_depth_data > 200] = 255

    try:
        os.stat(args.save_dir)
    except OSError:
        os.makedirs(args.save_dir)

    cv2.imwrite('{}/predict.png'.format(args.save_dir), predict_img)

    if args.output_tif:
        driver = dsm.GetDriver()
        destImage = driver.Create('{}/CU_CLS_Float.tif'.format(args.save_dir),
                                  xsize=dsm.RasterXSize,
                                  ysize=dsm.RasterYSize,
                                  bands=1,
                                  eType=gdal.GDT_Float32)

        gdalnumeric.CopyDatasetInfo(dsm, destImage)

        full_predict = cv2.resize(pred, (dsm.RasterXSize, dsm.RasterYSize),
                                  interpolation=cv2.INTER_NEAREST)

        destBand = destImage.GetRasterBand(1)
        destBand.WriteArray(full_predict)
예제 #11
0
def rast_math(output_path, expression, *args):
    """
    A raster math calculator that uses GDALs python bindings instead of command line
    interface for simple math. The syntax of this feels more pythonic than the native gdal_calc.py
    syntax. Supports up to 26 raster images for each letter of the alphabet as
    arguments. Supports single band raster images or gdal.Band instances as *args inputs.
    Input expression will be directly evaluated, so users can input numerical constants
    and simple ``numpy`` or ``math`` module operators with lowercase function names. Because
    this function uses python bindings and numpy data structures, be mindful of the memory
    limitations associated with 32-bit python, highly recommend using 64 bit.

    :param output_path: filepath at which to store expression result. Set to False to just return
                        the numeric array instead of saving it.
    :param expression:  the mathematical expression using rasters as A,B,C, etc
    :param args:        Filepaths to single band rasters that represent A,B,C, etc (in order)
                        OR, gdal.Band instances which could be used with multi-band rasters via...
                            ds = gdal.Open(rastpath)
                            bandA = ds.GetRasterBand(1)
                            bandB = ds.GetRasterBand(2)
                            rast_math(outpath, "numpy.log(A) + 3 * B", bandA, bandB)
    :return:            the output path to the file created by this function

    An example for the ubiquitous NDVI calculation from landsat bands:

    ..code-block: python

        root = r"my_landsat_directory"              # directory with landsat tiffs
        A_path = os.path.join(root, "tile_B5.TIF")  # filepath to Band5
        B_path = os.path.join(root, "tile_B4.TIF")  # filepath to Band4
        out_path = os.path.join(root, "NDVI.TIF")   # filepath of new output image

        rast_math(out_path, "(A + B) / (A - B)", A_path, B_path)

    An example where we want to conditionally mask one raster by another, say
    image "A" is our raster with data, and image "B" is a mask where a value of 1
    indicates a bad value, and zero indicates a good value.

    ..code-block:python

        rast_math(out_path, "A * (B == 0)", A_path, B_path)
    """

    # set up the iterators and structures
    datasets = {}  # dictionary where actual data will go
    eval_args = {}  # dictionary with string literals to be evaluated as code
    alphabet = string.ascii_uppercase[:len(args)]

    # format the expression with curly brackets around letters
    print("Executing expression '{0}'".format(expression))
    for letter in alphabet:
        expression = expression.replace(letter, "{%s}" % letter)

    # create the numpy arrays from raster datasets with gdal
    for arg, letter in zip(args, alphabet):

        # handle filepath input and raise exception for invalid filepaths
        if isinstance(arg, str):
            if not os.path.exists(arg):
                raise Exception("file {0} does not exist!".format(arg))

            print("\tLoading {0} as raster '{1}'".format(arg, letter))
            dataset_in = gdal.Open(arg, gdalconst.GA_ReadOnly)
            band = dataset_in.GetRasterBand(1)
            datasets[letter] = numpy.array(band.ReadAsArray(), dtype="float32")

        # handles input type of a gdal.Band instance
        elif isinstance(arg, gdal.Band):
            datasets[letter] = numpy.array(arg.ReadAsArray(), dtype="float32")

        eval_args[letter] = "datasets['{0}']".format(letter)

    # assemble and evaluate the expression
    eval_expression = expression.format(**eval_args)
    print(eval_expression)
    out_array = eval(eval_expression)

    # either save the output or return an output array
    if output_path:
        driver = gdal.GetDriverByName(
            "GTiff")  # create the geotiff driver object
        yshape, xshape = datasets["A"].shape  # set dimensions of output file
        num_bands = 1  # only supports single band output
        dataset_out = driver.Create(output_path, xshape, yshape, num_bands,
                                    gdal.GDT_Float32)
        gdalnumeric.CopyDatasetInfo(dataset_in, dataset_out)
        band_out = dataset_out.GetRasterBand(1)
        gdalnumeric.BandWriteArray(band_out, out_array)
        return os.path.abspath(output_path)

    else:
        return out_array