def checkPseudoAxis(axis, dataset=None, variable=None, ndim=(1, 2)): ''' detect coordinate variables and prepare for use in plotting ''' # get coordinate variable if isinstance(axis, basestring): if isinstance(dataset, Dataset): axis = dataset[axis] else: raise TypeError( "Need a Dataset object to look up coordinate variable (pseudo-axis): {}" .format(dataset)) elif not isinstance(axis, Variable): raise TypeError( "The coordinate variable (pseudo-axis) can either be a Variable object of a name: {}" .format(axis)) if axis.ndim not in ndim: raise AxisError( "Coordinate variable '{:s}' does not have a compatible number of dimensions: {:d}." .format(axis.name, axis.ndim)) # check against data variable if isinstance(variable, Variable): for ax in axis.axes: if not variable.hasAxis(ax.name): raise AxisError( "Coordinate Variable '{}' has Axis '{}', but the Axis is not present in the data Variable '{}'" .format(axis.name, ax.name, variable.name)) if axis.ndim == 2 and axis.shape != variable.shape: raise AxisError( "Coordinate variable '{:s}' does not have a compatible shape/dimensions: {}." .format(axis.name, axis.shape)) # return checked coordiante variable return axis
def apply_over_arrays(fct, *arrays, **kwargs): ''' similar to apply_along_axis, but operates on a list of ndarray's and is not parallelized ''' axis = kwargs.pop('axis', -1) lexitcode = kwargs.pop('lexitcode', False) lout = 'out' in kwargs # output array (for some numpy functions) # pre-process input (get reshaped views) arrays = [ collapseOuterDims(array, axis=axis, laddOuter=True) for array in arrays ] ie = arrays[0].shape[0] if not all(array.shape[0] == ie for array in arrays): raise AxisError("Cannot coerce input arrays into compatible shapes.") # special handling of output arrays if lout: out = collapseOuterDims(kwargs['out'], axis=axis, laddOuter=True) if out.shape[0] != ie: raise AxisError("Output array has incompatible shape.") # loop over outer dimension and apply function if lexitcode: ecs = [] # exit code for i in xrange(ie): arrslc = [array[i, :] for array in arrays] if lout: kwargs['out'] = out[i, :] ec = fct(*arrslc, **kwargs) if lexitcode: ecs.append(ec) #if lexitcode and not all(ecs): raise AssertionError("Some function executions were not successful!") # return output list (or None's if fct has no exit code) return ecs if lexitcode else None
def collapseOuterDims(ndarray, axis=None, laddOuter=True): ''' transform an n-dim array to a 2-dim array by collapsing all but the last/innermost dimension ''' if not isinstance(ndarray, np.ndarray): raise TypeError(ndarray) if ndarray.ndim < 2: if laddOuter: ndarray.reshape((1, ndarray.size)) else: raise AxisError(ndarray.shape) if axis is not None and not (axis == -1 or axis == ndarray.ndim - 1): if not isinstance(axis, (int, np.integer)): raise TypeError(axis) ndarray = np.rollaxis( ndarray, axis=axis, start=ndarray.ndim) # make desired axis innermost axis shape = (np.prod(ndarray.shape[:-1]), ndarray.shape[-1]) # new 2D shape ndarray = np.reshape(ndarray, shape) # just a new view return ndarray # return reshaped (and reordered) array
def loadEnKF_StnTS(folder=None, varlist='all', varatts=None, name='enkf', title='EnKF', basin=None, start_date=None, end_date=None, sampling=None, period=None, date_range=None, llastIncl=True, WSC_station=None, basin_list=None, filenames=None, prefix=None, time_axis='datetime', scalefactors=None, metadata=None, lkgs=False, out_dir='out/', yaml_file='../input_data/obs_meta.yaml', lYAML=True, nreal=None, ntime=None, **kwargs): ''' load EnKF ensemble data as formatted GeoPy Dataset ''' out_folder = os.path.join(folder, 'out/') # default output folder if not os.path.exists(out_folder): raise IOError(out_folder) # default values if isinstance(varlist, str) and varlist == 'hydro': varlist = Hydro.varlist elif isinstance(varlist, str) and varlist == 'obs': varlist = Obs.varlist elif isinstance(varlist, str) and varlist == 'all': varlist = Hydro.varlist + Obs.varlist elif not isinstance(varlist, (tuple, list)): raise TypeError(varlist) if varatts is None: varatts = variable_attributes.copy() varmap = { varatt['name']: enkf_name for enkf_name, varatt in list(varatts.items()) } varlist = [varmap[var] for var in varlist] # load WSC station meta data pass # initialize Dataset dataset = Dataset(name=name, title=title if title else name.title(), atts=metadata) ensemble = None time = None observation = None # load observation/innovation data if any([var in Obs.atts for var in varlist]): # load data vardata = loadObs(varlist=[var for var in varlist if var in Obs.atts], folder=out_folder, lpandas=False) ntime, nobs, nreal = list(vardata.values())[0].shape # create Axes if time is None: # figure out time axis time = timeAxis(start_date=start_date, end_date=end_date, sampling=sampling, date_range=date_range, time_axis=time_axis, llastIncl=llastIncl, ntime=ntime, varatts=varatts) elif len(time) != ntime: raise AxisError(time) if ensemble is None: # construct ensemble axis ensemble = Axis(atts=varatts['ensemble'], coord=np.arange(1, nreal + 1)) elif len(ensemble) != nreal: raise AxisError(ensemble) if observation is None: # construct ensemble axis observation = Axis(atts=varatts['observation'], coord=np.arange(1, nobs + 1)) elif len(observation) != nobs: raise AxisError(observation) # create variables for varname, data in list(vardata.items()): dataset += Variable(atts=varatts[varname], data=data, axes=(time, observation, ensemble)) # load YAML data, if available if lYAML: # load YAML file yaml_path = os.path.join(out_folder, yaml_file) if not os.path.exists(yaml_path): raise IOError(yaml_path) with open(yaml_path, 'r') as yf: obs_meta = yaml.load(yf) if obs_meta is None: raise IOError(yaml_path) # not a YAML file? # constant create variables for cvar, cval in list(obs_meta[0].items()): if isinstance(cval, str): dtype, missing = np.string_, '' elif isinstance(cval, (np.integer, int)): dtype, missing = np.int_, 0 elif isinstance(cval, (np.inexact, float)): dtype, missing = np.float_, np.NaN else: dtype = None # skip if dtype: data = np.asarray([ missing if obs[cvar] is None else obs[cvar] for obs in obs_meta ], dtype=dtype) if cvar in varatts: atts = varatts[cvar] else: atts = dict(name=cvar, units='') dataset += Variable(atts=atts, data=data, axes=(observation, )) elif ntime is None: # try to infer time dimension from backup.info file backup_info = os.path.join(folder, 'backup.info') if os.path.exists(backup_info): with open(backup_info, 'r') as bf: ntime = int(bf.readline()) # load discharge/hydrograph data if 'discharge' in varlist: data = loadHydro(folder=out_folder, nreal=nreal, ntime=ntime) ntime, nreal = data.shape if time is None: # figure out time axis time = timeAxis(start_date=start_date, end_date=end_date, sampling=sampling, date_range=date_range, time_axis=time_axis, llastIncl=llastIncl, ntime=ntime, varatts=varatts) elif len(time) != ntime: raise AxisError(time) if ensemble is None: # construct ensemble axis ensemble = Axis(atts=varatts['ensemble'], coord=np.arange(1, nreal + 1)) elif len(ensemble) != nreal: raise AxisError(ensemble) atts = varatts['discharge'] if lkgs: data *= 1000. if atts['units'] == 'm^3/s': atts['units'] = 'kg/s' dataset += Variable(atts=atts, data=data, axes=(time, ensemble)) # return formatted Dataset if scalefactors is not None and scalefactors != 1: raise NotImplementedError return dataset
def tabulate(data, row_idx=0, col_idx=1, header=None, labels=None, cell_str='{}', cell_idx=None, cell_fct=None, lflatten=False, mode='mylatex', filename=None, folder=None, lfeedback=True, **kwargs): ''' Create a nicely formatted table in the selected format ('mylatex' or call tabulate); cell_str controls formatting of each cell, and also supports multiple arguments along an axis. lflatten skips cell axis checking and lumps all remaining axes together. ''' # check input if not isinstance(data, np.ndarray): try: data = np.asarray(data) except: raise TypeError(data) if cell_idx is not None: if not data.ndim == 3: raise AxisError(cell_idx) elif lflatten: if not data.ndim >= 2: raise AxisError(cell_idx) elif not data.ndim == 2: raise AxisError(cell_idx) if not isinstance(cell_str, basestring): raise TypeError(cell_str) if cell_fct: if not callable(cell_fct): raise TypeError(cell_fct) lcellfct = True else: lcellfct = False if cell_idx >= data.ndim: raise AxisError(cell_idx) collen = data.shape[col_idx] rowlen = data.shape[row_idx] if row_idx < col_idx: col_idx -= 1 # this is a shortcut for later (data gets sliced by row) llabel = False lheader = False if labels: if len(labels) != rowlen: raise AxisError(data.shape) llabel = True if header: if llabel: if len(header) == collen: header = ('', ) + tuple(header) elif not len(header) == collen + 1: raise AxisError(header) elif not len(header) == collen: raise AxisError(header) lheader = True ## assemble table in nested list table = [] # list of rows if lheader: table.append(header) # first row # loop over rows for i in xrange(rowlen): row = [labels[i]] if labels else [] rowdata = data.take(i, axis=row_idx) # loop over columns for j in xrange(collen): celldata = rowdata.take(j, axis=col_idx) # pass data to string of function if isinstance(celldata, np.ndarray): if lflatten: celldata = celldata.ravel() elif celldata.ndim > 1: raise AxisError(celldata.shape) cell = cell_fct(celldata) if lcellfct else cell_str.format( *celldata) else: cell = cell_fct(celldata) if lcellfct else cell_str.format( celldata) # N.B.: cell_fct also has to return a string row.append(cell) table.append(row) ## now make table if mode.lower() == 'mylatex': # extract settings lhline = kwargs.pop('lhline', True) lheaderhline = kwargs.pop('lheaderhline', True) cell_del = kwargs.pop('cell_del', ' & ') # regular column delimiter line_brk = kwargs.pop( 'line_break', ' \\\\ \\hline' if lhline else ' \\\\') # escape backslash tab_begin = kwargs.pop('tab_begin', '') # by default, no tab environment tab_end = kwargs.pop('tab_end', '') # by default, no tab environment extra_hline = kwargs.pop( 'extra_hline', []) # row_idx or label with extra \hline command lpercent = kwargs.pop( 'lpercent', True) # escape the percent symbol (LaTeX comment) if lpercent: # escape percent signs table = [[cell.replace('%', '\%') for cell in row] for row in table] # align cells nrow = rowlen + 1 if lheader else rowlen ncol = collen + 1 if llabel else collen col_fmts = [] # column width for j in xrange(ncol): wd = 0 for i in xrange(nrow): wd = max(wd, len(table[i][j])) col_fmts.append('{{:^{:d}s}}'.format(wd)) # assemble table string string = tab_begin + '\n' if tab_begin else '' # initialize for i, row in enumerate(table): row = [ fmt_str.format(cell) for fmt_str, cell in zip(col_fmts, row) ] string += (' ' + row[0]) # first cell for cell in row[1:]: string += (cell_del + cell) string += line_brk # add latex line break if i in extra_hline or (llabel and row[0] in extra_hline): string += ' \\hline' if lheaderhline and i == 0: string += ' \\hline' # always put one behind the header string += '\n' # add actual line break if tab_end: string += (tab_end + '\n') else: # use the tabulate module (it's not standard, so import only when needed) from tabulate import tabulate string = tabulate(table, tablefmt=mode, **kwargs) # headers, floatfmt, numalign, stralign, missingval ## write to file if filename: if folder: filename = folder + '/' + filename f = open(filename, mode='w') f.write(string) # write entire string and nothing else f.close() if lfeedback: print(filename) # return string for printing return string
def checkVarlist(varlist, varname=None, ndim=1, bins=None, support=None, method='pdf', lflatten=False, bootstrap_axis='bootstrap', lignore=False): ''' helper function to pre-process the variable list ''' # N.B.: 'lignore' is currently not used # varlist is the list of variable objects that are to be plotted if isinstance(varlist, Variable): varlist = [varlist] elif isinstance(varlist, Dataset): if isinstance(varname, basestring): varlist = [varlist[varname]] elif isinstance(varname, (tuple, list)): varlist = [ varlist[name] if name in varlist else None for name in varname ] else: raise TypeError elif isinstance(varlist, (tuple, list, Ensemble)): if varname is not None: tmplist = [] for var in varlist: if isinstance(var, Variable): tmplist.append(var) elif isinstance(var, Dataset): if var.hasVariable(varname): tmplist.append(var[varname]) else: tmplist.append(None) else: raise TypeError varlist = tmplist del tmplist else: raise TypeError if not all([isinstance(var, (Variable, NoneType)) for var in varlist]): raise TypeError for var in varlist: if var is not None and var.data_array.size > 1: var.squeeze() # remove singleton dimensions # evaluate distribution variables on support/bins if bins is not None or support is not None: varlist = evalDistVars(varlist, bins=bins, support=support, method=method, ldatasetLink=True, bootstrap_axis=bootstrap_axis) # check axis: they need to have only one axes, which has to be the same for all! for var in varlist: if var is None: pass elif isinstance(ndim, (list, tuple)): if var.ndim not in ndim: raise AxisError( "Variable '{:s}' does not have compatible dimension(s): {:d}." .format(var.name, var.ndim)) elif var.ndim > ndim and not lflatten: raise AxisError( "Variable '{:s}' has more than {:d} dimension(s); consider squeezing." .format(var.name, ndim)) elif var.ndim < ndim: raise AxisError( "Variable '{:s}' has less than {:d} dimension(s); consider display as a line." .format(var.name, ndim)) # return cleaned-up and checkd variable list return varlist
def rasterDataset(name=None, title=None, vardefs=None, axdefs=None, atts=None, projection=None, griddef=None, lgzip=None, lgdal=True, lmask=True, fillValue=None, lskipMissing=True, lgeolocator=True, file_pattern=None, lfeedback=True, **kwargs): ''' function to load a set of variables that are stored in raster format in a systematic directory tree into a Dataset Variables and Axis are defined as follows: vardefs[varname] = dict(name=string, units=string, axes=tuple of strings, atts=dict, plot=dict, dtype=np.dtype, fillValue=value) axdefs[axname] = dict(name=string, units=string, atts=dict, coord=array or list) or None The path to raster files is constructed as variable_pattern+axes_pattern, where axes_pattern is defined through the axes, (as in rasterVarialbe) and variable_pattern takes the special keywords VAR, which is the variable key in vardefs. ''' ## prepare input data and axes if griddef: xlon, ylat = griddef.xlon, griddef.ylat if projection is None: projection = griddef.projection elif projection != griddef.projection: raise ArgumentError("Conflicting projection and GridDef!") geotransform = griddef.geotransform isProjected = griddef.isProjected else: xlon = ylat = geotransform = None isProjected = False if projection is None else True # construct axes dict axes = dict() for axname, axdef in axdefs.items(): assert 'coord' in axdef, axdef assert ('name' in axdef and 'units' in axdef) or 'atts' in axdef, axdef if axdef is None: axes[axname] = None else: ax = Axis(**axdef) axes[ax.name] = ax # check for map Axis if isProjected: if 'x' not in axes: axes['x'] = xlon if 'y' not in axes: axes['y'] = ylat else: if 'lon' not in axes: axes['lon'] = xlon if 'lat' not in axes: axes['lat'] = ylat ## load raster data into Variable objects varlist = [] for varname, vardef in vardefs.items(): # check definitions assert 'axes' in vardef and 'dtype' in vardef, vardef assert ('name' in vardef and 'units' in vardef) or 'atts' in vardef, vardef # determine relevant axes vardef = vardef.copy() axes_list = [ None if ax is None else axes[ax] for ax in vardef.pop('axes') ] # define path parameters (with varname) path_params = vardef.pop('path_params', None) path_params = dict() if path_params is None else path_params.copy() if 'VAR' not in path_params: path_params['VAR'] = varname # a special key # add kwargs and relevant axis indices relaxes = [ax.name for ax in axes_list if ax is not None] # relevant axes for key, value in kwargs.items(): if key not in axes or key in relaxes: vardef[key] = value # create Variable object var = rasterVariable(projection=projection, griddef=griddef, file_pattern=file_pattern, lgzip=lgzip, lgdal=lgdal, lmask=lmask, lskipMissing=lskipMissing, axes=axes_list, path_params=path_params, lfeedback=lfeedback, **vardef) # vardef components: name, units, atts, plot, dtype, fillValue varlist.append(var) # check that map axes are correct for ax in var.xlon, var.ylat: if axes[ax.name] is None: axes[ax.name] = ax elif axes[ax.name] != ax: raise AxisError("{} axes are incompatible.".format(ax.name)) if griddef is None: griddef = var.griddef elif griddef != var.griddef: raise AxisError("GridDefs are inconsistent.") if geotransform is None: geotransform = var.geotransform elif geotransform != var.geotransform: raise AxisError( "Conflicting geotransform (from Variable) and GridDef!\n {} != {}" .format(var.geotransform, geotransform)) ## create Dataset # create dataset dataset = Dataset(name=name, title=title, varlist=varlist, axes=axes, atts=atts) # add GDAL functionality dataset = addGDALtoDataset(dataset, griddef=griddef, projection=projection, geotransform=geotransform, gridfolder=None, lwrap360=None, geolocator=lgeolocator, lforce=False) # N.B.: for some reason we also need to pass the geotransform, otherwise it is recomputed internally and some consistency # checks fail due to machine-precision differences # return GDAL-enabled Dataset return dataset
def readRasterArray(file_pattern, lgzip=None, lgdal=True, dtype=np.float32, lmask=True, fillValue=None, lfeedback=False, lgeotransform=True, axes=None, lna=False, lskipMissing=False, path_params=None, **kwargs): ''' function to load a multi-dimensional numpy array from several structured ASCII raster files ''' if axes is None: raise NotImplementedError #TODO: implement automatic detection of axes arguments and axes order ## expand path argument and figure out dimensions # collect axes arguments shape = [] axes_kwargs = dict() for ax in axes: if ax not in kwargs: raise AxisError(ax) coord = kwargs.pop(ax) shape.append(len(coord)) axes_kwargs[ax] = coord assert len(axes) == len(shape) == len(axes_kwargs) shape = tuple(shape) #TODO: add handling of embedded inner product expansion # argument expansion using outer product file_kwargs_list = expandArgumentList(outer_list=axes, **axes_kwargs) assert np.prod(shape) == len(file_kwargs_list) ## load data from raster files and assemble array path_params = dict() if path_params is None else path_params.copy( ) # will be modified # find first valid 2D raster to determine shape i0 = 0 path_params.update(file_kwargs_list[i0]) # update axes parameters filepath = file_pattern.format(**path_params) # construct file name if not os.path.exists(filepath): if lskipMissing: # find first valid while not os.path.exists(filepath): i0 += 1 # go to next raster file if i0 >= len(file_kwargs_list): raise IOError( "No valid input raster files found!\n'{}'".format( filepath)) if lfeedback: print ' ', path_params.update( file_kwargs_list[i0]) # update axes parameters filepath = file_pattern.format(**path_params) # nest in line else: # or raise error raise IOError(filepath) # read first 2D raster file data2D = readASCIIraster(filepath, lgzip=lgzip, lgdal=lgdal, dtype=dtype, lna=True, lmask=lmask, fillValue=fillValue, lgeotransform=lgeotransform, **kwargs) if lgeotransform: data2D, geotransform0, na = data2D else: data2D, na = data2D # we might still need na, but no need to check if it is the same shape2D = data2D.shape # get 2D raster shape for later use # allocate data array list_shape = (np.prod(shape), ) + shape2D # assume 3D shape to concatenate 2D rasters if lmask: data = ma.empty(list_shape, dtype=dtype) if fillValue is None: data._fill_value = data2D._fill_value else: data._fill_value = fillValue data.mask = True # initialize everything as masked else: data = np.empty(list_shape, dtype=dtype) # allocate the array assert data.shape[0] == len(file_kwargs_list), (data.shape, len(file_kwargs_list)) # insert (up to) first raster before continuing if lskipMissing and i0 > 0: data[: i0, :, :] = ma.masked if lmask else fillValue # mask all invalid rasters up to first valid raster data[i0, :, :] = data2D # add first (valid) raster # loop over remaining 2D raster files for i, file_kwargs in enumerate(file_kwargs_list[i0:]): path_params.update(file_kwargs) # update axes parameters filepath = file_pattern.format(**path_params) # construct file name if os.path.exists(filepath): if lfeedback: print '.', # indicate data with bar/pipe # read 2D raster file data2D = readASCIIraster(filepath, lgzip=lgzip, lgdal=lgdal, dtype=dtype, lna=False, lmask=lmask, fillValue=fillValue, lgeotransform=lgeotransform, **kwargs) # check geotransform if lgeotransform: data2D, geotransform = data2D if not geotransform == geotransform0: raise AxisError( geotransform ) # to make sure all geotransforms are identical! else: geotransform = None # size information if not shape2D == data2D.shape: raise AxisError( data2D.shape ) # to make sure all geotransforms are identical! # insert 2D raster into 3D array data[i + i0, :, :] = data2D # raster shape has to match elif lskipMissing: # fill with masked values data[i + i0, :, :] = ma.masked # mask missing raster if lfeedback: print ' ', # indicate missing with dot else: raise IOError(filepath) # complete feedback with linebreak if lfeedback: print '' # reshape and check dimensions assert i + i0 == data.shape[0] - 1, (i, i0) data = data.reshape(shape + shape2D) # now we have the full shape gc.collect() # remove duplicate data # return data and optional meta data if lgeotransform or lna: return_data = (data, ) if lgeotransform: return_data += (geotransform, ) if lna: return_data += (na, ) else: return_data = data return return_data
def rasterVariable(name=None, units=None, axes=None, atts=None, plot=None, dtype=None, projection=None, griddef=None, file_pattern=None, lgzip=None, lgdal=True, lmask=True, fillValue=None, lskipMissing=True, path_params=None, offset=0, scalefactor=1, transform=None, time_axis=None, lfeedback=False, **kwargs): ''' function to read multi-dimensional raster data and construct a GDAL-enabled Variable object ''' # print status if lfeedback: print "Loading variable '{}': ".format(name), # no newline ## figure out axes arguments and load data # figure out axes (list/tuple of axes has to be ordered correctly!) axes_list = [ax.name for ax in axes[:-2]] # N.B.: the last two axes are the two horizontal map axes (x&y); they can be None and will be inferred from raster # N.B.: coordinate values can be overridden with keyword arguments, but length must be consistent # figure out coordinates for axes for ax in axes[:-2]: if ax.name in kwargs: # just make sure the dimensions match, but use keyword argument if not len(kwargs[ax.name]) == len(ax): raise AxisError( "Length of Variable axis and raster file dimension have to be equal." ) else: # use Axis coordinates and add to kwargs for readRasterArray call kwargs[ax.name] = tuple(ax.coord) # load raster data if lfeedback: print("'{}'".format(file_pattern)) data, geotransform = readRasterArray(file_pattern, lgzip=lgzip, lgdal=lgdal, dtype=dtype, lmask=lmask, fillValue=fillValue, lgeotransform=True, axes=axes_list, lna=False, lskipMissing=lskipMissing, path_params=path_params, lfeedback=lfeedback, **kwargs) # shift and rescale if offset != 0: data += offset if scalefactor != 1: data *= scalefactor ## create Variable object and add GDAL # check map axes and generate if necessary xlon, ylat = getAxes( geotransform, xlen=data.shape[-1], ylen=data.shape[-2], projected=griddef.isProjected if griddef else bool(projection)) axes = list(axes) if axes[-1] is None: axes[-1] = xlon elif len(axes[-1]) != len(xlon): raise AxisError(axes[-1]) if axes[-2] is None: axes[-2] = ylat elif len(axes[-2]) != len(ylat): raise AxisError(axes[-2]) # create regular Variable with data in memory var = Variable(name=name, units=units, axes=axes, data=data, dtype=dtype, mask=None, fillValue=fillValue, atts=atts, plot=plot) # apply transform (if any), now that we have axes etc. if transform is not None: var = transform(var=var, time_axis=time_axis) # add GDAL functionality if griddef is not None: # perform some consistency checks ... if projection is None: projection = griddef.projection elif projection != griddef.projection: raise ArgumentError( "Conflicting projection and GridDef!\n {} != {}".format( projection, griddef.projection)) if not np.isclose(geotransform, griddef.geotransform).all(): raise ArgumentError( "Conflicting geotransform (from raster) and GridDef!\n {} != {}" .format(geotransform, griddef.geotransform)) # ... and use provided geotransform (due to issues with numerical precision, this is usually better) geotransform = griddef.geotransform # if we don't pass the geotransform explicitly, it will be recomputed from the axes # add GDAL functionality var = addGDALtoVar(var, griddef=griddef, projection=projection, geotransform=geotransform, gridfolder=None) # return final, GDAL-enabled variable return var