def mark_range(bin_low, bin_high): """ nan out contiguous areas in F which fall within the given range, and satisfy the feature area requirement. alters F, from above """ in_bin = (dem.F >= bin_low) & (dem.F <= bin_high) marks = field.SimpleGrid(F=in_bin, extents=dem.extents) # find contiguous areas of this value: label, num_features = ndi.label(marks.F) labels = field.SimpleGrid(F=label, extents=dem.extents) feat_sizes = np.bincount(labels.F.ravel()) # Good features success = False for feat_i, feat_size in enumerate(feat_sizes): if feat_i == 0: continue if feat_size > feat_size_thresh: success = True sel = (labels.F == feat_i) # mask[ sel ] = True F[sel] = np.nan return success
def lidar_mask(): mask = np.isfinite(levee_burn().F) mask_open = ndimage.binary_dilation(mask, iterations=5) lidar_mask = field.SimpleGrid(extents=levee_burn().extents, F=mask_open) return lidar_mask
def lidar_resamp(): lidar_resamp_fn = 'lidar-levees.tif' if not os.path.exists(lidar_resamp_fn): # Will compile the lidar data here: lidar_levees = field.SimpleGrid(extents=lidar_mask().extents, F=np.zeros(lidar_mask().F.shape, np.float32)) lidar_levees.F[:, :] = np.nan lidar_resamp = lidar_src.to_grid(bounds=lidar_mask().extents, dx=lidar_mask().dx, dy=lidar_mask().dy) lidar_resamp.F[~lidar_mask().F] = np.nan # There are seams, at least 1px wide, between lidar tiles. lidar_resamp.fill_by_convolution(iterations=1) lidar_resamp.write_gdal('lidar-levees.tif') else: lidar_resamp = field.GdalGrid('lidar-levees.tif') return lidar_resamp
F.size, len(heap)) vis_depth, vis_idx = heapq.heappop(heap) assert flood_order[vis_idx] == -1 flood_order[vis_idx] = count count += 1 maybe_push((vis_idx[0] - 1, vis_idx[1])) maybe_push((vis_idx[0] + 1, vis_idx[1])) maybe_push((vis_idx[0], vis_idx[1] - 1)) maybe_push((vis_idx[0], vis_idx[1] + 1)) ## flood_order_f = field.SimpleGrid(F=flood_order, extents=dem.extents) plt.figure(2).clf() fig, ax = plt.subplots(num=2) zoom = (570996, 571427, 4155174, 4155536) # flood_order_f.crop(zoom).plot(ax=ax,interpolation='nearest',cmap='jet') sub_flood_order_f = flood_order_f.downsample(5) sub_flood_order_f.F = np.ma.array(sub_flood_order_f.F, mask=sub_flood_order_f.F < 0) sub_flood_order_f.plot(ax=ax, interpolation='nearest', cmap='jet') ##
def add_coamps_fields(model, cache_dir, fields=[('air_temp', 'Tair'), ('rltv_hum', 'RH')], mask_field=None): """ Uses existing times, assumed already hourly, in model.met_ds. fields: list of tuples, (coamps_name, suntans_name), e.g. [ ('air_temp','Tair'), ... ] to grab air_temp from coamps, and save to the Tair field of met_ds. mask: stompy.spatial.Field, evaluates to nonzero for areas that should be masked out. """ period_start = model.met_ds.nt.values[0] period_stop = model.met_ds.nt.values[-1] coamps_fields = [fld[0] for fld in fields] src_ds = coamps.coamps_dataset(model.grid.bounds(), period_start, period_stop, cache_dir=cache_dir, fields=coamps_fields) # only include points which are within 3km of the model domain g_poly = geometry.Polygon( model.grid.boundary_polygon().exterior).buffer(3000) fld = field.SimpleGrid(extents=[ src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0], src_ds.y.values[-1] ], F=src_ds[coamps_fields[0]].isel(time=0).values) logging.info("coamps temp: gridded resolution: %.2f %.2f" % (fld.dx, fld.dy)) # RH 2019-08-01: some nan values sneak through the other masks. try to # masking out any pixels that are nan on this initial time slice. time_slc = (src_ds.time.values >= period_start) & (src_ds.time.values <= period_stop) src_sub_ds = src_ds.isel(time=time_slc) # make sure these are aligned # somehow there are some 1 second errors in the met times. # allow slop up to 10 minutes assert len(model.met_ds.nt.values) == len( src_sub_ds.time.values ), "Mismatch in length of met_ds time and coamps ds time" assert np.all( np.abs(model.met_ds.nt.values - src_sub_ds.time.values) < np.timedelta64(600, 's')) X, Y = np.meshgrid(src_ds.x.values, src_ds.y.values) XY = np.stack((X, Y), axis=-1) base_valid = fld.polygon_mask(g_poly) if mask_field is not None: # additionally mask by a field passed in base_valid = base_valid & (mask_field(XY) == 0.0) met_ds = model.met_ds for coamps_name, sun_name in fields: all_values = src_sub_ds[coamps_name].values # additionally restrict to pixels where this variable is finite # over all time steps valid = base_valid & np.all(np.isfinite(all_values), axis=0) values = src_sub_ds[coamps_name].values[:, valid] # moved mask code inside the loop in order to catch all nan values # even if they are finite at the start, and nan later. xcoords = X[valid] ycoords = Y[valid] for v in [sun_name, 'x_' + sun_name, 'y_' + sun_name, 'z_' + sun_name]: if v in met_ds: del met_ds[v] met_ds['x_' + sun_name] = ("N" + sun_name), xcoords met_ds['y_' + sun_name] = ("N" + sun_name), ycoords met_ds['z_' + sun_name] = ("N" + sun_name), 10.0 * np.ones_like(xcoords) # DBG: #if not np.all(np.isfinite(values)): # import pdb # each time step, 519 values are nan. # pdb.set_trace() assert np.all(np.isfinite(values)) met_ds[sun_name] = (('nt', 'N' + sun_name), values)
right=0.99, wspace=0.01, bottom=0.02, hspace=0.01, top=0.99) fig.savefig(os.path.join(fig_dir, "compare-4-lidar-north_marsh_pond.png"), dpi=200) ## # Show difference between cbec as-built and the 2011 lidar. cbec_on_2011 = cbec_dem.extract_tile(match=lidar2011) cbec_on_2011.name = cbec_dem.name delta = field.SimpleGrid(F=cbec_on_2011.F - lidar2011.F, extents=cbec_on_2011.extents) delta.name = f"[cbec {version}] - [2011 Lidar]" delta.smooth_by_convolution(iterations=4) if 0: plt.figure(2).clf() fig, axs = plt.subplots(1, 3, num=2) fig.set_size_inches([8.5, 4.5], forward=True) for ax, dem in zip(axs.ravel(), [cbec_on_2011, delta, lidar2011]): img = dem.plot(ax=ax, cmap=sst, vmin=0.5, vmax=3.5) ax.axis('tight') ax.axis('equal') ax.axis(zoom) ax.axis('off') ax.set_title(dem.name)
return mark_range(bin_low, bin_high) else: return test_recursive(bin_low, bin_high) # # not right, just stowing some code here. # mr=scipy.stats.mode(dem.F[in_bin]) # slow! # bin_high=bin_low=mr.mode[0] # This is pretty slow -- while test_recursive(min_val, max_val): pass ## out = field.SimpleGrid(F=F, extents=dem.extents) plt.figure(2).clf() fig, ax = plt.subplots(num=2) out.plot(ax=ax, interpolation='nearest', vmin=-2, vmax=4) fig.set_size_inches((6, 8), forward=True) ax.xaxis.set_visible(0) ax.yaxis.set_visible(0) fig.tight_layout() fig.savefig('remove_flat_ponds-v00.png') ## out.write_gdal('../sources/usgs_2m_remove_flat_ponds_v00.tif')
def add_wind_preblended(model, cache_dir, pad=np.timedelta64(3 * 3600, 's')): """ model: A HydroModel instance cache_dir: path for caching wind data pad: how far before/after the simulation the wind dataset should extend Add wind data from pre-blended netcdf output """ g = model.grid period_start = model.run_start - pad period_stop = model.run_stop + pad # note that this is a bit different than the sfei data # - already in UTC # - larger footprint, coarser grid # - natural neighbors run on the observed + COAMPS (thinned). blended_ds = blended_dataset(period_start, period_stop) # Not ready for other years assert blended_ds.time.values[ 0] <= period_start, "FIX: pre-blended wind only set up for some of 2017" assert blended_ds.time.values[ -1] >= period_stop, "FIX: pre-blended wind only set up for some of 2017" # buffer out the model domain a bit to get a generous footprint g_poly = geometry.Polygon(g.boundary_polygon().exterior).buffer(3000) # For each of the sources, which points will be included? # Add a mask variable for each dataset exclude_poly = None for src_name, src_ds in [('BLENDED', blended_ds)]: fld = field.SimpleGrid(extents=[ src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0], src_ds.y.values[-1] ], F=src_ds.wind_u.isel(time=0).values) logging.info("%s: gridded resolution: %.2f %.2f" % (src_name, fld.dx, fld.dy)) mask = fld.polygon_mask(g_poly) logging.info("%s: %d of %d samples fall within grid" % (src_name, mask.sum(), mask.size)) if exclude_poly is not None: omit = fld.polygon_mask(exclude_poly) mask = mask & (~omit) logging.info( "%s: %d of %d samples fall within exclusion poly, will use %d" % (src_name, omit.sum(), omit.size, mask.sum())) src_ds['mask'] = src_ds.wind_u.dims[1:], mask # Trim to the same period time_slc = (blended_ds.time.values >= period_start) & (blended_ds.time.values <= period_stop) blended_sub_ds = blended_ds.isel(time=time_slc) times = blended_sub_ds.time.values # Now we start to break down the interface with model, as wind is not really # ready to go. met_ds = model.zero_met(times=times) srcs = [blended_sub_ds] src_counts = [src.mask.values.sum() for src in srcs] n_points = np.sum(src_counts) xcoords = [] ycoords = [] for src in srcs: X, Y = np.meshgrid(src.x.values, src.y.values) xcoords.append(X[src.mask.values]) ycoords.append(Y[src.mask.values]) xcoords = np.concatenate(xcoords) ycoords = np.concatenate(ycoords) # Replace placeholder coordinates for wind variables. for name in ['Uwind', 'Vwind']: del met_ds["x_" + name] del met_ds["y_" + name] del met_ds["z_" + name] del met_ds[name] met_ds["x_" + name] = ("N" + name), xcoords met_ds["y_" + name] = ("N" + name, ), ycoords met_ds["z_" + name] = ("N" + name, ), 10.0 * np.ones_like(xcoords) Uwind_t = [] Vwind_t = [] for ti in utils.progress(range(len(times)), msg="Compiling wind: %s"): Uwind = [] Vwind = [] for src in srcs: Uwind.append(src.wind_u.isel(time=ti).values[src.mask]) Vwind.append(src.wind_v.isel(time=ti).values[src.mask]) Uwind = np.concatenate(Uwind) Vwind = np.concatenate(Vwind) Uwind_t.append(Uwind) Vwind_t.append(Vwind) met_ds['Uwind'] = ('nt', "NUwind"), np.stack(Uwind_t) met_ds['Vwind'] = ('nt', "NVwind"), np.stack(Vwind_t) logging.info("New Met Dataset:") logging.info(str(met_ds)) model.met_ds = met_ds if int(model.config['metmodel']) not in [4, 5]: logging.warning("While adding wind, noticed metmodel %s" % (model.config['metmodel']))
def add_wind_coamps_sfei(model, cache_dir, pad=np.timedelta64(3 * 3600, 's'), coamps_buffer=30e3, air_temp=False): """ model: A HydroModel instance cache_dir: path for caching wind data pad: how far before/after the simulation the wind dataset should extend Combine SFEI interpolated winds and COAMPS winds. coamps_buffer: coamps samples within this distance of SFEI data are omitted. This method does not work so well with SUNTANS. The available interpolation methods (inverse distance and kriging) do not deal well with having two distinct, densely sampled datasets with a gap in between. air_temp: if 'coamps', fill in air temperature samples from coamps data. """ g = model.grid period_start = model.run_start - pad period_stop = model.run_stop + pad fields = ['wnd_utru', 'wnd_vtru', 'pres_msl'] if air_temp == 'coamps': # may add sol_rad at some point... fields += ['air_temp', 'rltv_hum'] coamps_ds = coamps.coamps_dataset(g.bounds(), period_start, period_stop, cache_dir=cache_dir, fields=fields) sfei_ds = xr.open_dataset('wind_natneighbor_WY2017.nc') # SFEI data is PST logging.info(sfei_ds.time.values[0]) sfei_ds.time.values[:] += np.timedelta64(8 * 3600, 's') logging.info(sfei_ds.time.values[0]) # just to be sure it took. sfei_ds.time.attrs['timezone'] = 'UTC' # Not ready for other years assert sfei_ds.time.values[ 0] <= period_start, "FIX: SFEI wind only setup for 2017" assert sfei_ds.time.values[ -1] >= period_stop, "FIX: SFEI wind only setup for 2017" # buffer out the model domain a bit to get a generous footprint g_poly = geometry.Polygon(g.boundary_polygon().exterior).buffer(3000) # For each of the sources, which points will be included? # Add a mask variable for each dataset exclude_poly = None for src_name, src_ds in [('SFEI', sfei_ds), ('COAMPS', coamps_ds)]: fld = field.SimpleGrid(extents=[ src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0], src_ds.y.values[-1] ], F=src_ds.wind_u.isel(time=0).values) logging.info("%s: gridded resolution: %.2f %.2f" % (src_name, fld.dx, fld.dy)) mask = fld.polygon_mask(g_poly) logging.info("%s: %d of %d samples fall within grid" % (src_name, mask.sum(), mask.size)) if exclude_poly is not None: omit = fld.polygon_mask(exclude_poly) mask = mask & (~omit) logging.info( "%s: %d of %d samples fall within exclusion poly, will use %d" % (src_name, omit.sum(), omit.size, mask.sum())) src_ds['mask'] = src_ds.wind_u.dims[1:], mask # Add these points to the exclusion polygon for successive sources X, Y = fld.XY() xys = np.c_[X[mask], Y[mask]] pnts = [geometry.Point(xy[0], xy[1]) for xy in xys] poly = cascaded_union([p.buffer(coamps_buffer) for p in pnts]) if exclude_poly is None: exclude_poly = poly else: exclude_poly = exclude_poly.union(poly) # Trim to the same period # SFEI time_slc = (sfei_ds.time.values >= period_start) & (sfei_ds.time.values <= period_stop) sfei_sub_ds = sfei_ds.isel(time=time_slc) # COAMPS time_slc = (coamps_ds.time.values >= period_start) & (coamps_ds.time.values <= period_stop) coamps_sub_ds = coamps_ds.isel(time=time_slc) # make sure that worked: assert np.all(sfei_sub_ds.time.values == coamps_sub_ds.time.values) times = sfei_sub_ds.time.values # Now we start to break down the interface with model, as wind is not really # ready to go. met_ds = model.zero_met(times=times) srcs = [sfei_sub_ds, coamps_sub_ds] src_counts = [src.mask.values.sum() for src in srcs] n_points = np.sum(src_counts) xcoords = [] ycoords = [] for src in srcs: X, Y = np.meshgrid(src.x.values, src.y.values) xcoords.append(X[src.mask.values]) ycoords.append(Y[src.mask.values]) xcoords = np.concatenate(xcoords) ycoords = np.concatenate(ycoords) # Replace placeholder coordinates for wind variables. for name in ['Uwind', 'Vwind']: del met_ds["x_" + name] del met_ds["y_" + name] del met_ds["z_" + name] del met_ds[name] met_ds["x_" + name] = ("N" + name), xcoords met_ds["y_" + name] = ("N" + name, ), ycoords met_ds["z_" + name] = ("N" + name, ), 10.0 * np.ones_like(xcoords) Uwind_t = [] Vwind_t = [] for ti in utils.progress(range(len(times)), msg="Compiling wind: %s"): Uwind = [] Vwind = [] for src in srcs: Uwind.append(src.wind_u.isel(time=ti).values[src.mask]) Vwind.append(src.wind_v.isel(time=ti).values[src.mask]) Uwind = np.concatenate(Uwind) Vwind = np.concatenate(Vwind) Uwind_t.append(Uwind) Vwind_t.append(Vwind) met_ds['Uwind'] = ('nt', "NUwind"), np.stack(Uwind_t) met_ds['Vwind'] = ('nt', "NVwind"), np.stack(Vwind_t) logging.info("New Met Dataset:") logging.info(str(met_ds)) model.met_ds = met_ds if int(model.config['metmodel']) not in [0, 4]: logging.warning("Adding wind, will override metmodel %s => %d" % (model.config['metmodel'], 4)) model.config['metmodel'] = 4 # wind only
zoom = (647088.821657404, 647702.7390601198, 4185484.2206095303, 4185941.6880934904) plt.figure(10).clf() fig, ax = plt.subplots(num=10) dem_at_adcp = dem(np.c_[adcp_ds.x, adcp_ds.y]) delta = dem_at_adcp - adcp_ds.z_bed scat = ax.scatter(adcp_ds.x, adcp_ds.y, 20, delta, cmap='coolwarm') scat.set_clim([-0.75, 0.75]) plt.colorbar(scat, ax=ax, orientation='horizontal', label='dem - adcp') ax.axis('equal') ax.axis(zoom) ## # And difference between the ADCP interpolated bathy dem_delta = field.SimpleGrid(extents=dem_adcp.extents, F=dem_adcp.F - dem.F) plt.figure(11).clf() fig, ax = plt.subplots(num=11) img = dem_delta.plot(cmap='coolwarm', clim=[-1, 1]) plt.colorbar(img, ax=ax, orientation='horizontal', label='dem_adcp - dem_dwr') ax.axis('equal') ax.axis(zoom)
## total_crop = [6321753, 6325212, 2116503, 2118419] # that covers the junction dem = field.GdalGrid("../bathy/dwr/OldRiver_SJ_072020171.tif", geo_bounds=total_crop) dem.F[dem.F < -1e+10] = np.nan ## # that's in ft # lowpass in 2D at 15ft dx = 20 dem_lp = dem.copy() dem_lp.smooth_by_convolution(dx) dem_hp = field.SimpleGrid(extents=dem.extents, F=dem.F - dem_lp.F) dem_rms = dem_hp.copy() dem_rms.F = dem_rms.F**2 dem_rms.smooth_by_convolution(dx) dem_rms.F = np.sqrt(dem_rms.F) ## plt.figure(1).clf() fig, axs = plt.subplots(1, 4, sharex=True, sharey=True, num=1) img1 = dem.plot(vmin=-20, vmax=5, cmap='jet', ax=axs[0]) img2 = dem_lp.plot(vmin=-20, vmax=5, cmap='jet', ax=axs[1]) img3 = dem_hp.plot(vmin=-1, vmax=1, cmap='seismic', ax=axs[2]) img4 = dem_rms.plot(vmin=0.1, vmax=1.0, cmap='plasma_r', ax=axs[3])
def base_dem(res=2.0): F = np.zeros((int(W / res), int(L / res)), np.float64) f = field.SimpleGrid(extents=[0, L, 0, W], F=F) x, y = f.xy() f.F[:, :] = (-y / 10)[:, None] return f
# Out in Grizzly seed_xy = np.array([583638, 4218191.]) ## z_inundate = dem.copy() z_inundate.F[:, :] = np.nan from scipy import ndimage for thresh in np.arange(0, 4.0, 0.01): print(thresh) wet = (leveed_dem.F <= thresh).astype(np.int8) labels, n_features = ndimage.label(wet) labeled = field.SimpleGrid(extents=leveed_dem.extents, F=labels) bay_label = labeled(seed_xy) sel = (labels == bay_label) & np.isnan(z_inundate.F) z_inundate.F[sel] = thresh z_inundate.write_gdal('inundation_sea_level.tif') ## plt.figure(2).clf() fig, axs = plt.subplots(1, 2, num=2) img = z_inundate.plot(ax=axs[0], cmap=turbo) img.set_clim(1.5, 2.5) topo_cmap = scmap.load_gradient('oc-sst.cpt') img2 = leveed_dem.plot(ax=axs[1], cmap=topo_cmap, vmin=0.0, vmax=3.0) plt.colorbar(img, ax=axs[0],
def to_grid(self,nx=None,ny=None,bounds=None,dx=None,dy=None): """ render the layers to a SimpleGrid tile. """ # boil the arguments down to dimensions if bounds is None: xmin,xmax,ymin,ymax = self.bounds() else: if len(bounds) == 2: xmin,ymin = bounds[0] xmax,ymax = bounds[1] else: xmin,xmax,ymin,ymax = bounds if nx is None: nx=1+int(np.round((xmax-xmin)/dx)) ny=1+int(np.round((ymax-ymin)/dy)) # allocate the blank starting canvas result_F =np.ones((ny,nx),'f8') result_F[:]=-999 result_data=field.SimpleGrid(extents=bounds,F=result_F) result_alpha=result_data.copy() result_alpha.F[:]=0.0 # Which sources to use, and in what order? box=geometry.box(bounds[0],bounds[2],bounds[1],bounds[3]) # Which sources are relevant? relevant_srcs=np.nonzero( [ box.intersects(geom) for geom in self.sources['geom'] ])[0] # omit negative priorities relevant_srcs=relevant_srcs[ self.src_priority[relevant_srcs]>=0 ] # Starts with lowest, goes to highest order = np.argsort(self.src_priority[relevant_srcs]) ordered_srcs=relevant_srcs[order] for src_i in ordered_srcs: print self.sources['src_name'][src_i] print " data mode: %s alpha mode: %s"%(self.data_mode[src_i], self.alpha_mode[src_i]) source=self.load_source(src_i) src_data = source.to_grid(bounds=bounds,dx=dx,dy=dy) src_alpha= field.SimpleGrid(extents=src_data.extents, F=np.ones(src_data.F.shape,'f8')) if 0: # slower src_alpha.mask_outside(self.sources['geom'][src_i],value=0.0) else: mask=src_alpha.polygon_mask(self.sources['geom'][src_i]) src_alpha.F[~mask] = 0.0 # create an alpha tile. depending on alpha_mode, this may draw on the lower data, # the polygon and/or the data tile. # modify the data tile according to the data mode - so if the data mode is # overlay, do nothing. but if it's max, the resulting data tile is the max # of itself and the lower data. # composite the data tile, using its alpha to blend with lower data. # the various operations def min(): """ new data will only decrease values """ valid=result_alpha.F>0 src_data.F[valid]=np.minimum( src_data.F[valid],result_data.F[valid] ) def max(): """ new data will only increase values """ valid=result_alpha.F>0 src_data.F[valid]=np.maximum( src_data.F[valid],result_data.F[valid] ) def fill(dist): "fill in small missing areas" pixels=int(round(float(dist)/dx)) niters=np.maximum( pixels//3, 2 ) src_data.fill_by_convolution(iterations=niters) def overlay(): pass # alpha channel operations: def valid(): # updates alpha channel to be zero where source data is missing. data_missing=np.isnan(src_data.F) src_alpha.F[data_missing]=0.0 def gaussian(dist): "smooth with gaussian filter - this allows spreading beyond original poly!" pixels=int(round(float(dist)/dx)) src_alpha.F=ndimage.gaussian_filter(src_alpha.F,pixels) def feather(dist): "linear feathering within original poly" pixels=int(round(float(dist)/dx)) Fsoft=ndimage.distance_transform_bf(src_alpha.F) src_alpha.F = (Fsoft/pixels).clip(0,1) # dangerous! executing code from a shapefile! eval(self.data_mode[src_i]) eval(self.alpha_mode[src_i]) data_missing=np.isnan(src_data.F) src_alpha.F[data_missing]=0.0 cleaned=src_data.F.copy() cleaned[data_missing]=-999 # avoid nan contamination. assert np.allclose( result_data.extents, src_data.extents ) assert np.all( result_data.F.shape==src_data.F.shape ) # before getting into fancy modes, just stack it all up: result_data.F = result_data.F *(1-src_alpha.F) + cleaned*src_alpha.F result_alpha.F = result_alpha.F*(1-src_alpha.F) + src_alpha.F # fudge it a bit, and allow semi-transparent data back out, but # at least nan out the totally transparent stuff. result_data.F[ result_alpha.F==0 ] = np.nan return result_data
txy00 = dict(name='ADCP RBF cluster=3k med=3', f=field.GdalGrid('adcp-rbf-cluster-med3.tif')) txy01 = dict(name='DEM RBF qtracks', f=field.GdalGrid('tile-rbf-qtracks.tif')) txy02 = dict(name='DEM RBF cluster', f=field.GdalGrid('tile-rbf-cluster.tif')) txy03 = dict(name='DEM RBF cluster, 3x3 median', f=field.GdalGrid('tile-rbf-cluster-med3.tif')) zha00 = dict(name='Zhang', f=field.GdalGrid('zhang-dem.tif')) zbd00 = dict(name='Zhang bidir', f=field.GdalGrid('zhang-dem-bidir.tif')) candidates = [txy00, txy01, txy02, txy03, zha00, zbd00] truth = tile ## # bitmask of samples which are valid in all outputs mask = field.SimpleGrid(extents=truth.extents, F=np.isfinite(truth.F)) # Get them all on the same grid, interpolating onto the tile for cand in candidates: f = cand['f'] if (f.extents == truth.extents) and (f.dx == truth.dx) and (f.dy == truth.dy): cand['fm'] = f else: print("%s not aligned. Interpolating" % cand['name']) cand['fm'] = f.extract_tile(match=truth, interpolation='bilinear') mask.F = mask.F & np.isfinite(cand['fm'].F) ##